Merge SPL-2019-07-05

Change-Id: I00781c96be237a526aadb8d7e9e1221a12cd3b1c
diff --git a/Android.mk b/Android.mk
index 5d3db9a..5ac0ecf 100644
--- a/Android.mk
+++ b/Android.mk
@@ -12,12 +12,18 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SRC_FILES := \
+        $(call all-java-files-under, src) \
+        $(call all-java-files-under, ../../../vendor/qcom/opensource/bluetooth_ext/packages_apps_bluetooth_ext/src) \
+        $(call all-java-files-under, ../../../vendor/qcom/opensource/commonsys/bluetooth_ext/packages_apps_bluetooth_ext/src)
+
 LOCAL_PACKAGE_NAME := Bluetooth
 LOCAL_PRIVATE_PLATFORM_APIS := true
 LOCAL_CERTIFICATE := platform
 LOCAL_USE_AAPT2 := true
 LOCAL_JNI_SHARED_LIBRARIES := libbluetooth_jni
+
 LOCAL_JAVA_LIBRARIES := javax.obex telephony-common services.net
 LOCAL_STATIC_JAVA_LIBRARIES := \
         com.android.vcard \
@@ -27,7 +33,11 @@
         libprotobuf-java-lite \
         bluetooth-protos-lite
 
-LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v4
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+        android-support-v4
+LOCAL_STATIC_JAVA_LIBRARIES += com.android.emailcommon
+LOCAL_PROTOC_OPTIMIZE_TYPE := micro
+
 LOCAL_REQUIRED_MODULES := libbluetooth
 LOCAL_PROGUARD_ENABLED := disabled
 include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b24064f..ebafdbc 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -24,6 +24,7 @@
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
     <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
+    <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" />
     <uses-permission android:name="android.permission.BLUETOOTH_MAP" />
     <uses-permission android:name="android.permission.DUMP" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
@@ -58,7 +59,6 @@
     <uses-permission android:name="android.permission.SEND_SMS" />
     <uses-permission android:name="android.permission.READ_SMS" />
     <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
     <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
     <uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
@@ -68,6 +68,8 @@
     <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
     <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+    <uses-permission android:name="com.android.email.permission.ACCESS_PROVIDER"/>
+    <uses-permission android:name="com.android.email.permission.READ_ATTACHMENT"/>
 
     <!-- For PBAP Owner Vcard Info -->
     <uses-permission android:name="android.permission.READ_PROFILE"/>
@@ -335,6 +337,14 @@
         </service>
         <service
             android:process="@string/process"
+            android:name = ".ba.BATService"
+            android:enabled="@bool/profile_supported_ba">
+            <intent-filter>
+                <action android:name="android.bluetooth.IBluetoothBATransmitter" />
+            </intent-filter>
+        </service>
+        <service
+            android:process="@string/process"
             android:name = ".hid.HidHostService"
             android:enabled="@bool/profile_supported_hid_host">
             <intent-filter>
diff --git a/jni/Android.bp b/jni/Android.bp
index c0fdd80..bab3d12 100644
--- a/jni/Android.bp
+++ b/jni/Android.bp
@@ -25,6 +25,7 @@
     include_dirs: [
         "libnativehelper/include/nativehelper",
         "system/bt/types",
+        "vendor/qcom/opensource/commonsys/bluetooth_ext/vhal/include",
     ],
     shared_libs: [
         "libandroid_runtime",
@@ -38,6 +39,7 @@
         "libbluetooth-types",
         "libutils",
         "libcutils",
+        "libbluetoothqti_jni",
     ],
     cflags: [
         "-Wall",
diff --git a/jni/com_android_bluetooth.h b/jni/com_android_bluetooth.h
index 9f136a0..d09d1df 100644
--- a/jni/com_android_bluetooth.h
+++ b/jni/com_android_bluetooth.h
@@ -97,7 +97,15 @@
 
 int register_com_android_bluetooth_sdp (JNIEnv* env);
 
+int register_com_android_bluetooth_btservice_vendor (JNIEnv* env);
+
+int register_com_android_bluetooth_btservice_vendor_socket(JNIEnv* env);
+
 int register_com_android_bluetooth_hearing_aid(JNIEnv* env);
+
+int register_com_android_bluetooth_avrcp_ext(JNIEnv* env);
+
+int register_com_android_bluetooth_ba(JNIEnv* env);
 }
 
 #endif /* COM_ANDROID_BLUETOOTH_H */
diff --git a/jni/com_android_bluetooth_a2dp_sink.cpp b/jni/com_android_bluetooth_a2dp_sink.cpp
index 50c5087..91de5af 100644
--- a/jni/com_android_bluetooth_a2dp_sink.cpp
+++ b/jni/com_android_bluetooth_a2dp_sink.cpp
@@ -231,7 +231,7 @@
 
 int register_com_android_bluetooth_a2dp_sink(JNIEnv* env) {
   return jniRegisterNativeMethods(
-      env, "com/android/bluetooth/a2dpsink/A2dpSinkStateMachine", sMethods,
+      env, "com/android/bluetooth/a2dpsink/A2dpSinkService", sMethods,
       NELEM(sMethods));
 }
 }
diff --git a/jni/com_android_bluetooth_avrcp.cpp b/jni/com_android_bluetooth_avrcp.cpp
index 1eb3553..fc72517 100644
--- a/jni/com_android_bluetooth_avrcp.cpp
+++ b/jni/com_android_bluetooth_avrcp.cpp
@@ -25,6 +25,8 @@
 
 #include <inttypes.h>
 #include <string.h>
+#include <mutex>
+#include <shared_mutex>
 
 namespace android {
 static jmethodID method_getRcFeatures;
@@ -46,6 +48,7 @@
 
 static const btrc_interface_t* sBluetoothAvrcpInterface = NULL;
 static jobject mCallbacksObj = NULL;
+static std::shared_timed_mutex callbacks_mutex;
 
 /* Function declarations */
 static bool copy_item_attributes(JNIEnv* env, jobject object,
@@ -61,6 +64,7 @@
 static void btavrcp_remote_features_callback(const RawAddress& bd_addr,
                                              btrc_remote_features_t features) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
 
   if (!mCallbacksObj) {
@@ -84,6 +88,7 @@
 /** Callback for play status request */
 static void btavrcp_get_play_status_callback(const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
 
   if (!mCallbacksObj) {
@@ -107,6 +112,7 @@
                                               btrc_media_attr_t* p_attrs,
                                               const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
 
   if (!mCallbacksObj) {
@@ -140,6 +146,7 @@
                                                    uint32_t param,
                                                    const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
 
   if (!mCallbacksObj) {
@@ -163,6 +170,7 @@
 static void btavrcp_volume_change_callback(uint8_t volume, uint8_t ctype,
                                            const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
 
   if (!mCallbacksObj) {
@@ -187,6 +195,7 @@
 static void btavrcp_passthrough_command_callback(int id, int pressed,
                                                  const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
 
   if (!mCallbacksObj) {
@@ -210,6 +219,7 @@
 static void btavrcp_set_addressed_player_callback(uint16_t player_id,
                                                   const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
 
   if (!mCallbacksObj) {
@@ -233,6 +243,7 @@
 static void btavrcp_set_browsed_player_callback(uint16_t player_id,
                                                 const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
     ALOGE("%s: mCallbacksObj is null", __func__);
@@ -256,6 +267,7 @@
     uint8_t scope, uint32_t start_item, uint32_t end_item, uint8_t num_attr,
     uint32_t* p_attr_ids, const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
 
   if (!mCallbacksObj) {
@@ -296,6 +308,7 @@
 static void btavrcp_change_path_callback(uint8_t direction, uint8_t* folder_uid,
                                          const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
 
   if (!mCallbacksObj) {
@@ -331,6 +344,7 @@
                                            btrc_media_attr_t* p_attrs,
                                            const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
 
   if (!mCallbacksObj) {
@@ -374,6 +388,7 @@
                                        uint8_t* uid,
                                        const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
     ALOGE("%s: mCallbacksObj is null", __func__);
@@ -406,6 +421,7 @@
 static void btavrcp_get_total_num_items_callback(uint8_t scope,
                                                  const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
     ALOGE("%s: mCallbacksObj is null", __func__);
@@ -428,6 +444,7 @@
 static void btavrcp_search_callback(uint16_t charset_id, uint16_t str_len,
                                     uint8_t* p_str, const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
     ALOGE("%s: mCallbacksObj is null", __func__);
@@ -460,6 +477,7 @@
                                               uint16_t uid_counter,
                                               const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
     ALOGE("%s: mCallbacksObj is null", __func__);
@@ -563,6 +581,7 @@
 }
 
 static void initNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   const bt_interface_t* btInf = getBluetoothInterface();
   if (btInf == NULL) {
     ALOGE("Bluetooth module is not loaded");
@@ -600,6 +619,7 @@
 }
 
 static void cleanupNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   const bt_interface_t* btInf = getBluetoothInterface();
   if (btInf == NULL) {
     ALOGE("Bluetooth module is not loaded");
@@ -746,18 +766,18 @@
       env->ReleaseByteArrayElements(address, addr, 0);
       return JNI_FALSE;
     }
-  }
 
-  for (int attr_cnt = 0; attr_cnt < numAttr; ++attr_cnt) {
-    pAttrs[attr_cnt].attr_id = attr[attr_cnt];
-    ScopedLocalRef<jstring> text(
-        env, (jstring)env->GetObjectArrayElement(textArray, attr_cnt));
+    for (int attr_cnt = 0; attr_cnt < numAttr; ++attr_cnt) {
+      pAttrs[attr_cnt].attr_id = attr[attr_cnt];
+      ScopedLocalRef<jstring> text(
+          env, (jstring)env->GetObjectArrayElement(textArray, attr_cnt));
 
-    if (!copy_jstring(pAttrs[attr_cnt].text, BTRC_MAX_ATTR_STR_LEN, text.get(),
-                      env)) {
-      rspStatus = BTRC_STS_INTERNAL_ERR;
-      ALOGE("%s: Failed to copy attributes", __func__);
-      break;
+      if (!copy_jstring(pAttrs[attr_cnt].text, BTRC_MAX_ATTR_STR_LEN, text.get(),
+                        env)) {
+        rspStatus = BTRC_STS_INTERNAL_ERR;
+        ALOGE("%s: Failed to copy attributes", __func__);
+        break;
+      }
     }
   }
   RawAddress rawAddress;
@@ -1231,24 +1251,29 @@
   if (rspStatus == BTRC_STS_NO_ERROR) {
     if (depth > 0) {
       p_folders = new btrc_br_folder_name_t[depth];
-    }
 
-    for (int folder_idx = 0; folder_idx < depth; folder_idx++) {
-      /* copy folder names */
-      ScopedLocalRef<jstring> text(
-          env, (jstring)env->GetObjectArrayElement(textArray, folder_idx));
-
-      if (!copy_jstring(p_folders[folder_idx].p_str, BTRC_MAX_ATTR_STR_LEN,
-                        text.get(), env)) {
-        rspStatus = BTRC_STS_INTERNAL_ERR;
-        delete[] p_folders;
-        env->ReleaseByteArrayElements(address, addr, 0);
-        ALOGE("%s: Failed to copy folder name", __func__);
+      if (!p_folders ) {
+        jniThrowIOException(env, EINVAL);
+        ALOGE("%s: not have enough memeory", __func__);
         return JNI_FALSE;
       }
+      for (int folder_idx = 0; folder_idx < depth; folder_idx++) {
+        /* copy folder names */
+        ScopedLocalRef<jstring> text(
+            env, (jstring)env->GetObjectArrayElement(textArray, folder_idx));
 
-      p_folders[folder_idx].str_len =
-          strlen((char*)p_folders[folder_idx].p_str);
+        if (!copy_jstring(p_folders[folder_idx].p_str, BTRC_MAX_ATTR_STR_LEN,
+                          text.get(), env)) {
+          rspStatus = BTRC_STS_INTERNAL_ERR;
+          delete[] p_folders;
+          env->ReleaseByteArrayElements(address, addr, 0);
+          ALOGE("%s: Failed to copy folder name", __func__);
+          return JNI_FALSE;
+        }
+
+        p_folders[folder_idx].str_len =
+            strlen((char*)p_folders[folder_idx].p_str);
+      }
     }
   }
 
diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp
index 7710a91..cc7bb64 100644
--- a/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -21,6 +21,7 @@
 #include "android_runtime/AndroidRuntime.h"
 #include "com_android_bluetooth.h"
 #include "hardware/bt_rc.h"
+#include "hardware/bt_vendor_rc.h"
 #include "utils/Log.h"
 
 #include <string.h>
@@ -35,6 +36,7 @@
 static jmethodID method_handleSetAbsVolume;
 static jmethodID method_handleRegisterNotificationAbsVol;
 static jmethodID method_handletrackchanged;
+static jmethodID method_handleElementAttrupdate;
 static jmethodID method_handleplaypositionchanged;
 static jmethodID method_handleplaystatuschanged;
 static jmethodID method_handleGetFolderItemsRsp;
@@ -51,6 +53,7 @@
 static jclass class_AvrcpPlayer;
 
 static const btrc_ctrl_interface_t* sBluetoothAvrcpInterface = NULL;
+static const btrc_vendor_ctrl_interface_t* sBluetoothAvrcpVendorInterface = NULL;
 static jobject sCallbacksObj = NULL;
 
 static void btavrcp_passthrough_response_callback(const RawAddress& bd_addr,
@@ -117,7 +120,7 @@
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
                                    (jbyte*)&bd_addr.address);
   sCallbackEnv->CallVoidMethod(sCallbacksObj, method_getRcFeatures, addr.get(),
-                               (jint)features);
+                               (jint)features, (jint) 0);
 }
 
 static void btavrcp_setplayerapplicationsetting_rsp_callback(
@@ -139,6 +142,86 @@
                                addr.get(), (jint)accepted);
 }
 
+static void btavrcp_get_vendor_rcfeatures_callback(RawAddress* bd_addr, int features,
+    uint16_t cover_art_psm) {
+  ALOGV("%s", __func__);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid()) return;
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr ");
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr);
+  sCallbackEnv->CallVoidMethod(sCallbacksObj, method_getRcFeatures, addr.get(),
+                               (jint)features, (jint) cover_art_psm);
+}
+
+static void  btavrcp_vendor_get_mediaelementattribute_rsp_callback(RawAddress *bd_addr,
+        uint8_t num_attr, btrc_element_attr_val_t *p_attrs) {
+   /*
+    * byteArray will be formatted like this: id,len,string
+    * Assuming text feild to be null terminated.
+    */
+    jbyteArray addr;
+    jintArray attribIds;
+    jobjectArray stringArray;
+    jstring str;
+    jclass strclazz;
+    jint i;
+    ALOGV("%s", __func__);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+
+    addr = sCallbackEnv->NewByteArray(sizeof(RawAddress));
+    if (!addr) {
+        ALOGE("Fail to get new array ");
+        return;
+    }
+    attribIds = sCallbackEnv->NewIntArray(num_attr);
+    if(!attribIds) {
+        ALOGE(" failed to set new array for attribIds");
+        sCallbackEnv->DeleteLocalRef(addr);
+        return;
+    }
+    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(RawAddress), (jbyte *) bd_addr);
+
+    strclazz = sCallbackEnv->FindClass("java/lang/String");
+    stringArray = sCallbackEnv->NewObjectArray((jint)num_attr, strclazz, 0);
+    if(!stringArray) {
+        ALOGE(" failed to get String array");
+        sCallbackEnv->DeleteLocalRef(addr);
+        sCallbackEnv->DeleteLocalRef(attribIds);
+        return;
+    }
+    for(i = 0; i < num_attr; i++)
+    {
+        str = sCallbackEnv->NewStringUTF((char*)(p_attrs[i].text));
+        if(!str) {
+            ALOGE(" Unable to get str ");
+            sCallbackEnv->DeleteLocalRef(addr);
+            sCallbackEnv->DeleteLocalRef(attribIds);
+            sCallbackEnv->DeleteLocalRef(stringArray);
+            return;
+        }
+        sCallbackEnv->SetIntArrayRegion(attribIds, i, 1, (jint*)&(p_attrs[i].attr_id));
+        sCallbackEnv->SetObjectArrayElement(stringArray, i,str);
+        sCallbackEnv->DeleteLocalRef(str);
+    }
+
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleElementAttrupdate, addr,
+         (jbyte)(num_attr), attribIds, stringArray);
+    sCallbackEnv->DeleteLocalRef(addr);
+    sCallbackEnv->DeleteLocalRef(attribIds);
+    /* TODO check do we need to delete str seperately or not */
+    sCallbackEnv->DeleteLocalRef(stringArray);
+    sCallbackEnv->DeleteLocalRef(strclazz);
+}
+
 static void btavrcp_playerapplicationsetting_callback(
     const RawAddress& bd_addr, uint8_t num_attr,
     btrc_player_app_attr_t* app_attrs, uint8_t num_ext_attr,
@@ -367,6 +450,15 @@
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
+  ScopedLocalRef<jbyteArray> addr(
+    sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to get new array ");
+    return;
+  }
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)&bd_addr.address);
+
   // Inspect if the first element is a folder/item or player listing. They are
   // always exclusive.
   bool isPlayerListing =
@@ -540,10 +632,10 @@
 
   if (isPlayerListing) {
     sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetPlayerItemsRsp,
-                                 itemArray.get());
+                                 itemArray.get(), addr.get());
   } else {
     sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetFolderItemsRsp,
-                                 status, itemArray.get());
+                                 status, itemArray.get(), addr.get());
   }
 }
 
@@ -553,8 +645,17 @@
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
+  ScopedLocalRef<jbyteArray> addr(
+    sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to get new array ");
+    return;
+  }
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)&bd_addr.address);
+
   sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleChangeFolderRsp,
-                               (jint)count);
+                               (jint)count, addr.get());
 }
 
 static void btavrcp_set_browsed_player_callback(const RawAddress& bd_addr,
@@ -564,8 +665,17 @@
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
+  ScopedLocalRef<jbyteArray> addr(
+    sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to get new array ");
+    return;
+  }
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)&bd_addr.address);
+
   sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleSetBrowsedPlayerRsp,
-                               (jint)num_items, (jint)depth);
+                               (jint)num_items, (jint)depth, addr.get());
 }
 
 static void btavrcp_set_addressed_player_callback(const RawAddress& bd_addr,
@@ -575,8 +685,17 @@
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
+  ScopedLocalRef<jbyteArray> addr(
+    sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to get new array ");
+    return;
+  }
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)&bd_addr.address);
+
   sCallbackEnv->CallVoidMethod(
-      sCallbacksObj, method_handleSetAddressedPlayerRsp, (jint)status);
+      sCallbacksObj, method_handleSetAddressedPlayerRsp, (jint)status, addr.get());
 }
 
 static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
@@ -598,6 +717,12 @@
     btavrcp_set_browsed_player_callback,
     btavrcp_set_addressed_player_callback};
 
+static btrc_vendor_ctrl_callbacks_t  sBluetoothAvrcpVendorCallbacks = {
+    sizeof(sBluetoothAvrcpVendorCallbacks),
+    btavrcp_get_vendor_rcfeatures_callback,
+    btavrcp_vendor_get_mediaelementattribute_rsp_callback,
+};
+
 static void classInitNative(JNIEnv* env, jclass clazz) {
   method_handlePassthroughRsp =
       env->GetMethodID(clazz, "handlePassthroughRsp", "(II[B)V");
@@ -608,7 +733,7 @@
   method_onConnectionStateChanged =
       env->GetMethodID(clazz, "onConnectionStateChanged", "(ZZ[B)V");
 
-  method_getRcFeatures = env->GetMethodID(clazz, "getRcFeatures", "([BI)V");
+  method_getRcFeatures = env->GetMethodID(clazz, "getRcFeatures", "([BII)V");
 
   method_setplayerappsettingrsp =
       env->GetMethodID(clazz, "setPlayerAppSettingRsp", "([BB)V");
@@ -628,6 +753,9 @@
   method_handletrackchanged =
       env->GetMethodID(clazz, "onTrackChanged", "([BB[I[Ljava/lang/String;)V");
 
+  method_handleElementAttrupdate =
+       env->GetMethodID(clazz, "onElementAttributeUpdate", "([BB[I[Ljava/lang/String;)V");
+
   method_handleplaypositionchanged =
       env->GetMethodID(clazz, "onPlayPositionChanged", "([BII)V");
 
@@ -636,10 +764,10 @@
 
   method_handleGetFolderItemsRsp =
       env->GetMethodID(clazz, "handleGetFolderItemsRsp",
-                       "(I[Landroid/media/browse/MediaBrowser$MediaItem;)V");
+                       "(I[Landroid/media/browse/MediaBrowser$MediaItem;[B)V");
   method_handleGetPlayerItemsRsp = env->GetMethodID(
       clazz, "handleGetPlayerItemsRsp",
-      "([Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V");
+      "([Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;[B)V");
 
   method_createFromNativeMediaItem =
       env->GetMethodID(clazz, "createFromNativeMediaItem",
@@ -653,11 +781,11 @@
                        "(ILjava/lang/String;[BII)Lcom/android/bluetooth/"
                        "avrcpcontroller/AvrcpPlayer;");
   method_handleChangeFolderRsp =
-      env->GetMethodID(clazz, "handleChangeFolderRsp", "(I)V");
+      env->GetMethodID(clazz, "handleChangeFolderRsp", "(I[B)V");
   method_handleSetBrowsedPlayerRsp =
-      env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "(II)V");
+      env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "(II[B)V");
   method_handleSetAddressedPlayerRsp =
-      env->GetMethodID(clazz, "handleSetAddressedPlayerRsp", "(I)V");
+      env->GetMethodID(clazz, "handleSetAddressedPlayerRsp", "(I[B)V");
   ALOGI("%s: succeeds", __func__);
 }
 
@@ -676,6 +804,12 @@
     return;
   }
 
+  if (sBluetoothAvrcpVendorInterface != NULL) {
+    ALOGW("Cleaning up Avrcp Vendor Interface before initializing...");
+    sBluetoothAvrcpVendorInterface->cleanup_vendor();
+    sBluetoothAvrcpVendorInterface = NULL;
+  }
+
   if (sBluetoothAvrcpInterface != NULL) {
     ALOGW("Cleaning up Avrcp Interface before initializing...");
     sBluetoothAvrcpInterface->cleanup();
@@ -705,6 +839,28 @@
     return;
   }
 
+  sBluetoothAvrcpVendorInterface =
+      (btrc_vendor_ctrl_interface_t*)btInf->get_profile_interface(
+          BT_PROFILE_AV_RC_VENDOR_CTRL_ID);
+  if (sBluetoothAvrcpVendorInterface == NULL) {
+    ALOGE("Failed to get Bluetooth Avrcp Vendor Controller Interface");
+    sBluetoothAvrcpInterface->cleanup();
+    sBluetoothAvrcpInterface = NULL;
+    return;
+  }
+
+  status =
+      sBluetoothAvrcpVendorInterface->init_vendor(&sBluetoothAvrcpVendorCallbacks);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed to initialize Bluetooth Avrcp Vendor Controller, status: %d",
+          status);
+    sBluetoothAvrcpVendorInterface = NULL;
+    sBluetoothAvrcpInterface->cleanup();
+    sBluetoothAvrcpInterface = NULL;
+    return;
+  }
+
+
   sCallbacksObj = env->NewGlobalRef(object);
 }
 
@@ -715,6 +871,11 @@
     return;
   }
 
+  if (sBluetoothAvrcpVendorInterface != NULL) {
+    sBluetoothAvrcpVendorInterface->cleanup_vendor();
+    sBluetoothAvrcpVendorInterface = NULL;
+  }
+
   if (sBluetoothAvrcpInterface != NULL) {
     sBluetoothAvrcpInterface->cleanup();
     sBluetoothAvrcpInterface = NULL;
@@ -1054,6 +1215,49 @@
   env->ReleaseByteArrayElements(address, addr, 0);
 }
 
+/* This api is used to fetch metadata for currently playing track
+ * num_attribs: number of attributes to be fetched. 0 corresponds to fetch  all
+ *              attributes
+ * attrib_ids: list of attributes we want to fetch. NULL corresponds to fetch
+ *             all attributes.
+ */
+  static void getElementAttributesNative(JNIEnv *env, jobject object, jbyteArray address,
+                                        jbyte num_attribs, jbyteArray attrib_ids) {
+    if (!sBluetoothAvrcpVendorInterface) return;
+    bt_status_t status;
+    jbyte *addr;
+    uint32_t *pAttrs = NULL;
+    jbyte *attr;
+    int i;
+
+    if (!sBluetoothAvrcpInterface) return;
+    addr = env->GetByteArrayElements(address, NULL);
+    if (!addr) {
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+    if (num_attribs == 0) {
+        // we have to fetch all element attributes
+        sBluetoothAvrcpVendorInterface->get_media_element_attributes_vendor((RawAddress *)addr,
+                    (uint8_t)num_attribs, NULL);
+        env->ReleaseByteArrayElements(address, addr, 0);
+        return;
+    }
+    pAttrs = new uint32_t[num_attribs];
+    attr = env->GetByteArrayElements(attrib_ids, NULL);
+    for (i = 0; i < num_attribs; ++i) {
+        pAttrs[i] = (uint32_t)attr[i];
+    }
+    status = sBluetoothAvrcpVendorInterface->get_media_element_attributes_vendor(
+            (RawAddress *)addr, (uint8_t)num_attribs, pAttrs);
+    if (status != BT_STATUS_SUCCESS) {
+        ALOGE("Failed sending getElementAttributesNative command, status: %d", status);
+    }
+    delete[] pAttrs;
+    env->ReleaseByteArrayElements(address, addr, 0);
+    env->ReleaseByteArrayElements(attrib_ids, attr, 0);
+  }
+
 static JNINativeMethod sMethods[] = {
     {"classInitNative", "()V", (void*)classInitNative},
     {"initNative", "()V", (void*)initNative},
@@ -1075,6 +1279,7 @@
     {"playItemNative", "([BB[BI)V", (void*)playItemNative},
     {"setBrowsedPlayerNative", "([BI)V", (void*)setBrowsedPlayerNative},
     {"setAddressedPlayerNative", "([BI)V", (void*)setAddressedPlayerNative},
+    {"getElementAttributesNative", "([BB[B)V",(void *) getElementAttributesNative},
 };
 
 int register_com_android_bluetooth_avrcp_controller(JNIEnv* env) {
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index 976e388..1008aeb 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -70,12 +70,12 @@
   jmethodID constructor;
 } android_bluetooth_UidTraffic;
 
-static const bt_interface_t* sBluetoothInterface = NULL;
-static const btsock_interface_t* sBluetoothSocketInterface = NULL;
-static JNIEnv* callbackEnv = NULL;
+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 jobject sJniAdapterServiceObj = NULL;
+static jobject sJniCallbacksObj = NULL;
 static jfieldID sJniCallbacksField;
 
 namespace {
@@ -510,10 +510,12 @@
   jint status_;
 };
 
+#ifdef ENABLE_JAVA_WAKE_LOCKS
 static bool set_wake_alarm_callout(uint64_t delay_millis, bool should_wake,
                                    alarm_cb cb, void* data) {
   JNIThreadAttacher attacher;
   JNIEnv* env = attacher.getEnv();
+  jboolean ret = JNI_FALSE;
 
   if (env == nullptr) {
     ALOGE("%s: Unable to get JNI Env", __func__);
@@ -524,10 +526,15 @@
   sAlarmCallbackData = data;
 
   jboolean jshould_wake = should_wake ? JNI_TRUE : JNI_FALSE;
-  jboolean ret =
-      env->CallBooleanMethod(sJniAdapterServiceObj, method_setWakeAlarm,
+  if (sJniAdapterServiceObj) {
+      ret = env->CallBooleanMethod(sJniAdapterServiceObj, method_setWakeAlarm,
                              (jlong)delay_millis, jshould_wake);
+  } else {
+       ALOGE("%s JNI ERROR : JNI reference already cleaned : set_wake_alarm_callout", __func__);
+  }
+
   if (!ret) {
+    ALOGE("%s setWakeAlarm failed:ret= %d ", __func__, ret);
     sAlarmCallback = NULL;
     sAlarmCallbackData = NULL;
   }
@@ -548,9 +555,13 @@
   {
     ScopedLocalRef<jstring> lock_name_jni(env, env->NewStringUTF(lock_name));
     if (lock_name_jni.get()) {
-      bool acquired = env->CallBooleanMethod(
-          sJniAdapterServiceObj, method_acquireWakeLock, lock_name_jni.get());
-      if (!acquired) ret = BT_STATUS_WAKELOCK_ERROR;
+        if (sJniAdapterServiceObj) {
+            bool acquired = env->CallBooleanMethod(
+              sJniAdapterServiceObj, method_acquireWakeLock, lock_name_jni.get());
+            if (!acquired) ret = BT_STATUS_WAKELOCK_ERROR;
+        } else {
+          ALOGE("%s JNI ERROR : JNI reference already cleaned : acquire_wake_lock_callout", __func__);
+        }
     } else {
       ALOGE("%s unable to allocate string: %s", __func__, lock_name);
       ret = BT_STATUS_NOMEM;
@@ -573,9 +584,13 @@
   {
     ScopedLocalRef<jstring> lock_name_jni(env, env->NewStringUTF(lock_name));
     if (lock_name_jni.get()) {
-      bool released = env->CallBooleanMethod(
-          sJniAdapterServiceObj, method_releaseWakeLock, lock_name_jni.get());
-      if (!released) ret = BT_STATUS_WAKELOCK_ERROR;
+        if (sJniAdapterServiceObj) {
+            bool released = env->CallBooleanMethod(
+               sJniAdapterServiceObj, method_releaseWakeLock, lock_name_jni.get());
+            if (!released) ret = BT_STATUS_WAKELOCK_ERROR;
+         } else {
+          ALOGE("%s JNI ERROR : JNI reference already cleaned : release_wake_lock_callout", __func__);
+        }
     } else {
       ALOGE("%s unable to allocate string: %s", __func__, lock_name);
       ret = BT_STATUS_NOMEM;
@@ -584,7 +599,7 @@
 
   return ret;
 }
-
+#endif
 // 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) {
@@ -594,11 +609,12 @@
     ALOGE("%s() - Alarm fired with callback not set!", __func__);
   }
 }
-
+#ifdef ENABLE_JAVA_WAKE_LOCKS
 static bt_os_callouts_t sBluetoothOsCallouts = {
     sizeof(sBluetoothOsCallouts), set_wake_alarm_callout,
     acquire_wake_lock_callout, release_wake_lock_callout,
 };
+#endif
 
 #define PROPERTY_BT_LIBRARY_NAME "ro.bluetooth.library_name"
 #define DEFAULT_BT_LIBRARY_NAME "libbluetooth.so"
@@ -700,11 +716,14 @@
   }
 
   int ret = sBluetoothInterface->init(&sBluetoothCallbacks);
-  if (ret != BT_STATUS_SUCCESS) {
+  if (ret != BT_STATUS_SUCCESS && ret != BT_STATUS_DONE) {
     ALOGE("Error while setting the callbacks: %d\n", ret);
     sBluetoothInterface = NULL;
     return JNI_FALSE;
   }
+
+  /*disable these os_callout settings, so that native wake_lock will be enabled*/
+#ifdef ENABLE_JAVA_WAKE_LOCKS
   ret = sBluetoothInterface->set_os_callouts(&sBluetoothOsCallouts);
   if (ret != BT_STATUS_SUCCESS) {
     ALOGE("Error while setting Bluetooth callouts: %d\n", ret);
@@ -712,6 +731,7 @@
     sBluetoothInterface = NULL;
     return JNI_FALSE;
   }
+#endif
 
   sBluetoothSocketInterface =
       (btsock_interface_t*)sBluetoothInterface->get_profile_interface(
@@ -1161,6 +1181,11 @@
   const char** args = nullptr;
   if (numArgs > 0) args = new const char*[numArgs];
 
+  if (!args || !argObjs) {
+    ALOGE("%s: not have enough memeory", __func__);
+    return;
+  }
+
   for (int i = 0; i < numArgs; i++) {
     argObjs[i] = (jstring)env->GetObjectArrayElement(argArray, i);
     args[i] = env->GetStringUTFChars(argObjs[i], NULL);
@@ -1264,17 +1289,17 @@
 /*
  * JNI Initialization
  */
-jint JNI_OnLoad(JavaVM* jvm, void* reserved) {
-  JNIEnv* e;
-  int status;
+jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
+    JNIEnv *e;
+    int status;
 
-  ALOGV("Bluetooth Adapter Service : loading JNI\n");
+    ALOGV("Bluetooth Adapter Service : loading JNI\n");
 
-  // Check JNI version
-  if (jvm->GetEnv((void**)&e, JNI_VERSION_1_6)) {
-    ALOGE("JNI version mismatch error");
-    return JNI_ERR;
-  }
+    // Check JNI version
+    if (jvm->GetEnv((void **)&e, JNI_VERSION_1_6)) {
+        ALOGE("JNI version mismatch error");
+        return JNI_ERR;
+    }
 
   status = android::register_com_android_bluetooth_btservice_AdapterService(e);
   if (status < 0) {
@@ -1300,6 +1325,12 @@
     return JNI_ERR;
   }
 
+  status = android::register_com_android_bluetooth_ba(e);
+  if (status < 0) {
+      ALOGE("jni BA Transmitter registration failure: %d", status);
+      return JNI_ERR;
+  }
+
   status = android::register_com_android_bluetooth_a2dp_sink(e);
   if (status < 0) {
     ALOGE("jni a2dp sink registration failure: %d", status);
@@ -1359,11 +1390,29 @@
     return JNI_ERR;
   }
 
+  status = android::register_com_android_bluetooth_btservice_vendor(e);
+  if (status < 0) {
+    ALOGE("jni vendor registration failure: %d", status);
+    return JNI_ERR;
+  }
+
+  status = android::register_com_android_bluetooth_btservice_vendor_socket(e);
+  if (status < 0) {
+    ALOGE("jni vendor socket registration failure: %d", status);
+    return JNI_ERR;
+  }
+
   status = android::register_com_android_bluetooth_hearing_aid(e);
   if (status < 0) {
     ALOGE("jni hearing aid registration failure: %d", status);
     return JNI_ERR;
   }
 
+  status = android::register_com_android_bluetooth_avrcp_ext(e);
+  if (status < 0) {
+    ALOGE("jni avrcp_ext registration failure: %d", status);
+    return JNI_ERR;
+  }
+
   return JNI_VERSION_1_6;
 }
diff --git a/jni/com_android_bluetooth_gatt.cpp b/jni/com_android_bluetooth_gatt.cpp
index 86e667b..79726c2 100644
--- a/jni/com_android_bluetooth_gatt.cpp
+++ b/jni/com_android_bluetooth_gatt.cpp
@@ -1165,11 +1165,24 @@
 }
 
 static void gattSetScanParametersNative(JNIEnv* env, jobject object,
-                                        jint client_if, jint scan_interval_unit,
-                                        jint scan_window_unit) {
+                                        jint client_if, jint scan_phy,
+                                        jintArray scan_interval_unit,
+                                        jintArray scan_window_unit) {
+  std::vector<uint32_t> scan_interval = {0,0};
+  std::vector<uint32_t> scan_window = {0,0};
   if (!sGattIf) return;
+
+  int scan_int_cnt = env->GetArrayLength(scan_interval_unit);
+  if(scan_int_cnt > 0) {
+    env->GetIntArrayRegion(scan_interval_unit, 0, scan_int_cnt, (jint *)&scan_interval[0]);
+  }
+
+  int scan_window_cnt = env->GetArrayLength(scan_window_unit);
+  if(scan_window_cnt > 0) {
+    env->GetIntArrayRegion(scan_window_unit, 0, scan_window_cnt, (jint *)&scan_window[0]);
+  }
   sGattIf->scanner->SetScanParameters(
-      scan_interval_unit, scan_window_unit,
+      scan_phy, scan_interval, scan_window,
       base::Bind(&set_scan_params_cmpl_cb, client_if));
 }
 
@@ -2128,7 +2141,7 @@
      (void*)gattClientScanFilterClearNative},
     {"gattClientScanFilterEnableNative", "(IZ)V",
      (void*)gattClientScanFilterEnableNative},
-    {"gattSetScanParametersNative", "(III)V",
+    {"gattSetScanParametersNative", "(II[I[I)V",
      (void*)gattSetScanParametersNative},
 };
 
diff --git a/jni/com_android_bluetooth_hfp.cpp b/jni/com_android_bluetooth_hfp.cpp
index 8547aa6..4b01d8d 100644
--- a/jni/com_android_bluetooth_hfp.cpp
+++ b/jni/com_android_bluetooth_hfp.cpp
@@ -446,6 +446,7 @@
   if (!sBluetoothHfpInterface) {
     ALOGW("%s: Failed to get Bluetooth Handsfree Interface", __func__);
     jniThrowIOException(env, EINVAL);
+    return;
   }
   bt_status_t status =
       sBluetoothHfpInterface->Init(JniHeadsetCallbacks::GetInstance(),
diff --git a/jni/com_android_bluetooth_hid_host.cpp b/jni/com_android_bluetooth_hid_host.cpp
index 7838ff6..b8f4d65 100644
--- a/jni/com_android_bluetooth_hid_host.cpp
+++ b/jni/com_android_bluetooth_hid_host.cpp
@@ -24,7 +24,7 @@
 #include "utils/Log.h"
 
 #include <string.h>
-
+#include <shared_mutex>
 namespace android {
 
 static jmethodID method_onConnectStateChanged;
@@ -36,6 +36,7 @@
 
 static const bthh_interface_t* sBluetoothHidInterface = NULL;
 static jobject mCallbacksObj = NULL;
+static std::shared_timed_mutex mCallbacks_mutex;
 
 static jbyteArray marshall_bda(RawAddress* bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
@@ -53,6 +54,7 @@
 
 static void connection_state_callback(RawAddress* bd_addr,
                                       bthh_connection_state_t state) {
+  std::shared_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
@@ -72,6 +74,7 @@
 static void get_protocol_mode_callback(RawAddress* bd_addr,
                                        bthh_status_t hh_status,
                                        bthh_protocol_mode_t mode) {
+  std::shared_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
@@ -95,6 +98,7 @@
 
 static void get_report_callback(RawAddress* bd_addr, bthh_status_t hh_status,
                                 uint8_t* rpt_data, int rpt_size) {
+  std::shared_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
@@ -126,6 +130,7 @@
 static void virtual_unplug_callback(RawAddress* bd_addr,
                                     bthh_status_t hh_status) {
   ALOGV("call to virtual_unplug_callback");
+  std::shared_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
@@ -142,6 +147,7 @@
 }
 
 static void handshake_callback(RawAddress* bd_addr, bthh_status_t hh_status) {
+  std::shared_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
@@ -160,6 +166,7 @@
 
 static void get_idle_time_callback(RawAddress* bd_addr, bthh_status_t hh_status,
                                    int idle_time) {
+  std::shared_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -198,6 +205,7 @@
 }
 
 static void initializeNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
   const bt_interface_t* btInf = getBluetoothInterface();
   if (btInf == NULL) {
     ALOGE("Bluetooth module is not loaded");
@@ -234,6 +242,7 @@
 }
 
 static void cleanupNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
   const bt_interface_t* btInf = getBluetoothInterface();
 
   if (btInf == NULL) {
diff --git a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
index b959858..5040bdc 100644
--- a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
+++ b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
@@ -301,12 +301,12 @@
      * E.g. as a mapping for them such that the naming will match the underlying
      * matching folder ID's.
      */
-    public static final String FOLDER_NAME_INBOX = "INBOX";
-    public static final String FOLDER_NAME_SENT = "SENT";
-    public static final String FOLDER_NAME_OUTBOX = "OUTBOX";
-    public static final String FOLDER_NAME_DRAFT = "DRAFT";
-    public static final String FOLDER_NAME_DELETED = "DELETED";
-    public static final String FOLDER_NAME_OTHER = "OTHER";
+    public static final String FOLDER_NAME_INBOX = "inbox";
+    public static final String FOLDER_NAME_SENT = "sent";
+    public static final String FOLDER_NAME_OUTBOX = "outbox";
+    public static final String FOLDER_NAME_DRAFT = "draft";
+    public static final String FOLDER_NAME_DELETED = "deleted";
+    public static final String FOLDER_NAME_OTHER = "other";
 
     /**
      * Folder IDs to be used with Instant Messaging virtual folders
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 8440c77..5a8fb6e 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -133,4 +133,8 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"蓝牙音频已断开连接"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"蓝牙音频"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"无法传输 4GB 以上的文件"</string>
+   <!-- Bluetooth AVRCP Browsing Dialog-->
+    <string name="bluetooth_rc_feat_title">"蓝牙音频浏览"</string>
+    <string name="bluetooth_rc_feat_content">"对端支持该先进功能"</string>
+    <string name="bluetooth_rc_feat_subtext">"重新配对对端设备使其支持该功能"</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 4220c84..66e7d2d 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -133,4 +133,7 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"已中斷與藍牙音訊的連線"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"藍牙音訊"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"無法轉移大於 4GB 的檔案"</string>
+    <string name="bluetooth_rc_feat_title">"藍芽音頻瀏覽"</string>
+    <string name="bluetooth_rc_feat_content">"對端支持該先進功能"</string>
+    <string name="bluetooth_rc_feat_subtext">"重新配對對端設備使其支持該功能"</string>
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 9301e9d..cf2ceef 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -33,6 +33,7 @@
     <bool name="profile_supported_mapmce">false</bool>
     <bool name="profile_supported_hid_device">true</bool>
     <bool name="profile_supported_hearing_aid">false</bool>
+    <bool name="profile_supported_ba">false</bool>
 
     <!-- If true, we will require location to be enabled on the device to
          fire Bluetooth LE scan result callbacks in addition to having one
@@ -88,9 +89,11 @@
          value should be unique. -->
     <integer name="a2dp_source_codec_priority_sbc">1001</integer>
     <integer name="a2dp_source_codec_priority_aac">2001</integer>
-    <integer name="a2dp_source_codec_priority_aptx">3001</integer>
-    <integer name="a2dp_source_codec_priority_aptx_hd">4001</integer>
-    <integer name="a2dp_source_codec_priority_ldac">5001</integer>
+    <integer name="a2dp_source_codec_priority_ldac">3001</integer>
+    <integer name="a2dp_source_codec_priority_aptx">4001</integer>
+    <integer name="a2dp_source_codec_priority_aptx_hd">5001</integer>
+    <integer name="a2dp_source_codec_priority_aptx_adaptive">6001</integer>
+    <integer name="a2dp_source_codec_priority_aptx_tws">7001</integer>
 
     <!-- Package that is responsible for user interaction on pairing request,
          success or cancel.
@@ -105,4 +108,16 @@
     <!-- Flag whether or not to keep polling AG with CLCC for call information every 2 seconds -->
     <bool name="hfp_clcc_poll_during_call">true</bool>
 
+     <!-- Reload supported Bluetooth Profiles while BLE is turning ON -->
+     <bool name="reload_supported_profiles_when_enabled">true</bool>
+    <!-- For AVRCP cover art configuration If there is no update from UI
+         these default values would be used to fetch cover art.
+         height and width are to be  mentioned in pixels
+         maxsize is to be mentioned in bytes -->
+    <string name="avrcp_cover_art_default_mimetype">JPEG</string>
+    <string name="avrcp_cover_art_default_image_type">image</string>
+    <integer name="avrcp_cover_art_default_height">500</integer>
+    <integer name="avrcp_cover_art_default_width">500</integer>
+    <integer name="avrcp_cover_art_default_maxsize">200000</integer>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 17023e4..dcb992b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -248,4 +248,10 @@
     <string name="bluetooth_disconnected">Bluetooth audio disconnected"</string>
     <string name="a2dp_sink_mbs_label">Bluetooth Audio</string>
     <string name="bluetooth_opp_file_limit_exceeded">Files bigger than 4GB cannot be transferred</string>
+    <!-- Bluetooth AVRCP Browsing Dialog and Notification-->
+    <string name="avrcp_notification_name">BT ADVANCE FEATURE AVRCP</string>
+    <string name="bluetooth_advanced_feat_description">Bluetooth Advanced Browsing Feature</string>
+    <string name="bluetooth_rc_feat_title">Bluetooth Media Browsing</string>
+    <string name="bluetooth_rc_feat_content">Peer supports advanced feature</string>
+    <string name="bluetooth_rc_feat_subtext">Re-pair from peer to enable it</string>
 </resources>
diff --git a/src/com/android/bluetooth/ObexServerSockets.java b/src/com/android/bluetooth/ObexServerSockets.java
index 789f12f..4991038 100644
--- a/src/com/android/bluetooth/ObexServerSockets.java
+++ b/src/com/android/bluetooth/ObexServerSockets.java
@@ -81,19 +81,33 @@
     }
 
     /**
-     * Creates an Insecure RFCOMM {@link BluetoothServerSocket} and a L2CAP
-     *                  {@link BluetoothServerSocket}
+     * Creates a fixed Insecure RFCOMM {@link BluetoothServerSocket} and a L2CAP
+         *                  {@link BluetoothServerSocket}
      * @param validator a reference to the {@link IObexConnectionHandler} object to call
      *                  to validate an incoming connection.
+     * @param rfcommChannel is a fixed RFCOMM channnel to allocate
+     * @param l2capPsm is a fixed L2CAP PSM to allocate
      * @return a reference to a {@link ObexServerSockets} object instance.
      * @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
      */
-    public static ObexServerSockets createInsecure(IObexConnectionHandler validator) {
-        return create(validator, BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP,
-                BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false);
+    public static ObexServerSockets createInsecureWithFixedChannels(IObexConnectionHandler
+            validator, int rfcommChannel, int l2capPsm) {
+        return create(validator, rfcommChannel, l2capPsm , false);
     }
-
     private static final int CREATE_RETRY_TIME = 10;
+    /**
+     * Creates an RFCOMM {@link BluetoothServerSocket} and a L2CAP {@link BluetoothServerSocket}
+     * @param validator a reference to the {@link IObexConnectionHandler} object to call
+     *                  to validate an incoming connection.
+     * @param rfcommChannel fixed rfcomm channel number to listen on
+     * @param l2capPsm fixed l2cap psm to listen on
+     * @return a reference to a {@link ObexServerSockets} object instance.
+     * @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
+     */
+    public static ObexServerSockets createWithFixedChannels(IObexConnectionHandler validator,
+            int rfcommChannel, int l2capPsm) {
+        return create(validator, rfcommChannel, l2capPsm, true);
+    }
 
     /**
      * Creates an RFCOMM {@link BluetoothServerSocket} and a L2CAP {@link BluetoothServerSocket}
@@ -126,14 +140,14 @@
         for (int i = 0; i < CREATE_RETRY_TIME; i++) {
             initSocketOK = true;
             try {
-                if (rfcommSocket == null) {
+                if (rfcommSocket == null && rfcommChannel != -1) {
                     if (isSecure) {
                         rfcommSocket = bt.listenUsingRfcommOn(rfcommChannel);
                     } else {
                         rfcommSocket = bt.listenUsingInsecureRfcommOn(rfcommChannel);
                     }
                 }
-                if (l2capSocket == null) {
+                if (l2capSocket == null && l2capPsm != -1) {
                     if (isSecure) {
                         l2capSocket = bt.listenUsingL2capOn(l2capPsm);
                     } else {
@@ -143,6 +157,10 @@
             } catch (IOException e) {
                 Log.e(STAG, "Error create ServerSockets ", e);
                 initSocketOK = false;
+            } catch (SecurityException e) {
+                Log.e(STAG, "Error create ServerSockets ", e);
+                initSocketOK = false;
+                break;
             }
             if (!initSocketOK) {
                 // Need to break out of this loop if BT is being turned off.
@@ -205,12 +223,15 @@
         if (D) {
             Log.d(mTag, "startAccept()");
         }
+        if (mRfcommSocket != null) {
+            mRfcommThread = new SocketAcceptThread(mRfcommSocket);
+            mRfcommThread.start();
+        }
 
-        mRfcommThread = new SocketAcceptThread(mRfcommSocket);
-        mRfcommThread.start();
-
-        mL2capThread = new SocketAcceptThread(mL2capSocket);
-        mL2capThread.start();
+        if (mL2capSocket != null) {
+            mL2capThread = new SocketAcceptThread(mL2capSocket);
+            mL2capThread.start();
+        }
     }
 
     /**
@@ -221,7 +242,10 @@
      */
     private synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket conSocket) {
         if (D) {
-            Log.d(mTag, "onConnect() socket: " + conSocket);
+            Log.d(mTag, "onConnect() socket: " + conSocket + " mConHandler " + mConHandler);
+        }
+        if (mConHandler == null) {
+            return false;
         }
         return mConHandler.onConnect(device, conSocket);
     }
@@ -232,7 +256,8 @@
     private synchronized void onAcceptFailed() {
         shutdown(false);
         BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
-        if ((mAdapter != null) && (mAdapter.getState() == BluetoothAdapter.STATE_ON)) {
+        if ((mAdapter != null) && (mAdapter.getState() == BluetoothAdapter.STATE_ON)
+                && mConHandler != null) {
             Log.d(mTag, "onAcceptFailed() calling shutdown...");
             mConHandler.onAcceptFailed();
         }
diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java
index 457b053..bb6bf6c 100644
--- a/src/com/android/bluetooth/Utils.java
+++ b/src/com/android/bluetooth/Utils.java
@@ -64,6 +64,7 @@
     }
 
     public static byte[] getByteAddress(BluetoothDevice device) {
+        if (device == null) return new byte[BD_ADDR_LEN];
         return getBytesFromAddress(device.getAddress());
     }
 
diff --git a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
index 4de89df..b5fc3a1 100644
--- a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
+++ b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
@@ -21,9 +21,9 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
-
+import android.os.SystemProperties;
 import com.android.bluetooth.R;
-
+import com.android.bluetooth.btservice.AdapterService;
 /*
  * A2DP Codec Configuration setup.
  */
@@ -39,8 +39,10 @@
     private int mA2dpSourceCodecPriorityAac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
     private int mA2dpSourceCodecPriorityAptx = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
     private int mA2dpSourceCodecPriorityAptxHd = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+    private int mA2dpSourceCodecPriorityAptxAdaptive = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
     private int mA2dpSourceCodecPriorityLdac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-
+    private int mA2dpSourceCodecPriorityAptxTwsp = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+    //private int SOURCE_CODEC_TYPE_APTX_TWS = BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX + 1;
     A2dpCodecConfig(Context context, A2dpNativeInterface a2dpNativeInterface) {
         mContext = context;
         mA2dpNativeInterface = a2dpNativeInterface;
@@ -100,6 +102,7 @@
         }
 
         int value;
+        AdapterService mAdapterService = AdapterService.getAdapterService();
         try {
             value = resources.getInteger(R.integer.a2dp_source_codec_priority_sbc);
         } catch (NotFoundException e) {
@@ -129,61 +132,113 @@
                 < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
             mA2dpSourceCodecPriorityAptx = value;
         }
-
-        try {
-            value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd);
-        } catch (NotFoundException e) {
-            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-        }
-        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
-                < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
-            mA2dpSourceCodecPriorityAptxHd = value;
+        if(mAdapterService.isSplitA2DPSourceAPTXADAPTIVE()) {
+            try {
+                value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_adaptive);
+            } catch (NotFoundException e) {
+                value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+            }
+            if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+                    < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+                mA2dpSourceCodecPriorityAptxAdaptive = value;
+            }
+        } else {
+            mA2dpSourceCodecPriorityAptxAdaptive = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
         }
 
-        try {
-            value = resources.getInteger(R.integer.a2dp_source_codec_priority_ldac);
-        } catch (NotFoundException e) {
-            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+        if(mAdapterService.isSplitA2DPSourceAPTXHD()) {
+            try {
+                value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd);
+            } catch (NotFoundException e) {
+                value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+            }
+            if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+                    < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+                mA2dpSourceCodecPriorityAptxHd = value;
+            }
+        } else {
+            mA2dpSourceCodecPriorityAptxHd = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
         }
-        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
-                < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
-            mA2dpSourceCodecPriorityLdac = value;
+
+
+        if(mAdapterService.isSplitA2DPSourceLDAC()) {
+            try {
+                value = resources.getInteger(R.integer.a2dp_source_codec_priority_ldac);
+            } catch (NotFoundException e) {
+                value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+            }
+            if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+                    < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+                mA2dpSourceCodecPriorityLdac = value;
+            }
+        } else {
+            mA2dpSourceCodecPriorityLdac = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
+        }
+        if (mAdapterService.isVendorIntfEnabled()) {
+            try {
+                value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_tws);
+            } catch (NotFoundException e) {
+                value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+            }
+            if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+                    < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+                mA2dpSourceCodecPriorityAptxTwsp = value;
+            }
+        } else {
+            mA2dpSourceCodecPriorityAptxTwsp = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
         }
 
         BluetoothCodecConfig codecConfig;
-        BluetoothCodecConfig[] codecConfigArray =
+        BluetoothCodecConfig[] codecConfigArray;
+        int codecCount = 0;
+        codecConfigArray =
                 new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
+
         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                 mA2dpSourceCodecPrioritySbc, BluetoothCodecConfig.SAMPLE_RATE_NONE,
                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
                 .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
                 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
-        codecConfigArray[0] = codecConfig;
+        codecConfigArray[codecCount++] = codecConfig;
         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                 mA2dpSourceCodecPriorityAac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
                 .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
                 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
-        codecConfigArray[1] = codecConfig;
+        codecConfigArray[codecCount++] = codecConfig;
         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
                 mA2dpSourceCodecPriorityAptx, BluetoothCodecConfig.SAMPLE_RATE_NONE,
                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
                 .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
                 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
-        codecConfigArray[2] = codecConfig;
+        codecConfigArray[codecCount++] = codecConfig;
         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
                 mA2dpSourceCodecPriorityAptxHd, BluetoothCodecConfig.SAMPLE_RATE_NONE,
                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
                 .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
                 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
-        codecConfigArray[3] = codecConfig;
+        codecConfigArray[codecCount++] = codecConfig;
         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
                 mA2dpSourceCodecPriorityLdac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
                 .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
                 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
-        codecConfigArray[4] = codecConfig;
+        codecConfigArray[codecCount++] = codecConfig;
+        if (mAdapterService.isVendorIntfEnabled()) {
+            codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_TWSP,
+                    mA2dpSourceCodecPriorityAptxTwsp, BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                    BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
+                    .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
+                    0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
+            codecConfigArray[codecCount++] = codecConfig;
+        }
 
+        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_ADAPTIVE,
+                mA2dpSourceCodecPriorityAptxAdaptive, BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
+                .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
+                0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
+        codecConfigArray[codecCount++] = codecConfig;
         return codecConfigArray;
     }
 }
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 64a9cad..5c44e02 100644
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
 import android.bluetooth.BluetoothCodecConfig;
 import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
@@ -34,14 +35,20 @@
 import android.support.annotation.GuardedBy;
 import android.support.annotation.VisibleForTesting;
 import android.util.Log;
+import android.os.SystemProperties;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 
-import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.avrcp.Avrcp;
+import com.android.bluetooth.avrcp.Avrcp_ext;
 import com.android.bluetooth.avrcp.AvrcpTargetService;
 import com.android.bluetooth.btservice.AdapterService;
-import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.ba.BATService;
+import android.os.SystemClock;
+import com.android.bluetooth.gatt.GattService;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -59,11 +66,17 @@
     private static final String TAG = "A2dpService";
 
     private static A2dpService sA2dpService;
+    private static A2dpSinkService sA2dpSinkService;
+    private static boolean mA2dpSrcSnkConcurrency;
 
     private BluetoothAdapter mAdapter;
     private AdapterService mAdapterService;
     private HandlerThread mStateMachinesThread;
     private Avrcp mAvrcp;
+    private Avrcp_ext mAvrcp_ext;
+    private final Object mBtA2dpLock = new Object();
+    private final Object mBtAvrcpLock = new Object();
+    private final Object mActiveDeviceLock = new Object();
 
     @VisibleForTesting
     A2dpNativeInterface mA2dpNativeInterface;
@@ -74,16 +87,52 @@
     private BluetoothDevice mActiveDevice;
     private final ConcurrentMap<BluetoothDevice, A2dpStateMachine> mStateMachines =
             new ConcurrentHashMap<>();
+    private static final int[] CONNECTING_CONNECTED_STATES = {
+             BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED
+             };
+    // A2DP disconnet will be delayed at audioservice,
+    // follwoing flags capture delay and delay time.
+    private int mDisconnectDelay = 0;
+    private long mDisconnectTime = 0;
 
     // Upper limit of all A2DP devices: Bonded or Connected
     private static final int MAX_A2DP_STATE_MACHINES = 50;
     // Upper limit of all A2DP devices that are Connected or Connecting
     private int mMaxConnectedAudioDevices = 1;
+    private int mSetMaxConnectedAudioDevices = 1;
     // A2DP Offload Enabled in platform
     boolean mA2dpOffloadEnabled = false;
-
+    private boolean disconnectExisting = false;
+    private int EVENT_TYPE_NONE = 0;
+    private int mA2dpStackEvent = EVENT_TYPE_NONE;
     private BroadcastReceiver mBondStateChangedReceiver;
     private BroadcastReceiver mConnectionStateChangedReceiver;
+    private boolean mIsTwsPlusEnabled = false;
+    private boolean mIsTwsPlusMonoSupported = false;
+    private String  mTwsPlusChannelMode = "dual-mono";
+    private BluetoothDevice mDummyDevice = null;
+
+    private static final long AptxBLEScanMask = 0x3000;
+    private static final long Aptx_BLEScanEnable = 0x1000;
+    private static final long Aptx_BLEScanDisable = 0x2000;
+    private static final int SET_EBMONO_CFG = 1;
+    private static final int MonoCfg_Timeout = 5000;
+
+    private Handler mHandler = new Handler() {
+        @Override
+       public void handleMessage(Message msg)
+       {
+           switch (msg.what) {
+               case SET_EBMONO_CFG:
+                   Log.d(TAG, "setparameters to Mono");
+                   mAudioManager.setParameters("TwsChannelConfig=mono");
+                   mTwsPlusChannelMode = "mono";
+                   break;
+              default:
+                   break;
+           }
+       }
+    };
 
     @Override
     protected IProfileServiceBinder initBinder() {
@@ -99,7 +148,8 @@
     protected boolean start() {
         Log.i(TAG, "start()");
         if (sA2dpService != null) {
-            throw new IllegalStateException("start() called twice");
+            Log.w(TAG, "A2dpService is already running");
+            return true;
         }
 
         // Step 1: Get BluetoothAdapter, AdapterService, A2dpNativeInterface, AudioManager.
@@ -116,10 +166,36 @@
 
         // Step 2: Get maximum number of connected audio devices
         mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices();
+        mSetMaxConnectedAudioDevices = mMaxConnectedAudioDevices;
+        if (mAdapterService.isVendorIntfEnabled()) {
+            String twsPlusEnabled = SystemProperties.get("persist.vendor.btstack.enable.twsplus");
+            if (!twsPlusEnabled.isEmpty() && "true".equals(twsPlusEnabled)) {
+                mIsTwsPlusEnabled = true;
+            }
+            Log.i(TAG, "mMaxConnectedAudioDevices: " + mMaxConnectedAudioDevices);
+            if (mIsTwsPlusEnabled) {
+                mMaxConnectedAudioDevices = 2;
+            } else if (mMaxConnectedAudioDevices > 2) {
+                mMaxConnectedAudioDevices = 2;
+                mSetMaxConnectedAudioDevices = mMaxConnectedAudioDevices;
+            }
+            String twsPlusMonoEnabled = SystemProperties.get("persist.vendor.btstack.twsplus.monosupport");
+            if (!twsPlusMonoEnabled.isEmpty() && "true".equals(twsPlusMonoEnabled)) {
+                mIsTwsPlusMonoSupported = true;
+            }
+            String TwsPlusChannelMode = SystemProperties.get("persist.vendor.btstack.twsplus.defaultchannelmode");
+            if (!TwsPlusChannelMode.isEmpty() && "mono".equals(TwsPlusChannelMode)) {
+                mTwsPlusChannelMode = "mono";
+            }
+            Log.d(TAG, "Default TwsPlus ChannelMode: " + mTwsPlusChannelMode);
+        }
         Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices);
 
         // Step 3: Setup AVRCP
-        mAvrcp = Avrcp.make(this);
+        if(mAdapterService.isVendorIntfEnabled())
+            mAvrcp_ext = Avrcp_ext.make(this, this, mMaxConnectedAudioDevices);
+        else
+            mAvrcp = Avrcp.make(this);
 
         // Step 4: Start handler thread for state machines
         mStateMachines.clear();
@@ -138,6 +214,11 @@
         if (DBG) {
             Log.d(TAG, "A2DP offload flag set to " + mA2dpOffloadEnabled);
         }
+        mA2dpSrcSnkConcurrency= SystemProperties.getBoolean(
+                                "persist.vendor.service.bt.a2dp_concurrency", false);
+        if (DBG) {
+            Log.d(TAG, "A2DP concurrency set to " + mA2dpSrcSnkConcurrency);
+        }
 
         // Step 8: Setup broadcast receivers
         IntentFilter filter = new IntentFilter();
@@ -170,6 +251,8 @@
         if (mActiveDevice != null && AvrcpTargetService.get() != null) {
             AvrcpTargetService.get().storeVolumeForDevice(mActiveDevice);
         }
+        if (mActiveDevice != null && mAvrcp_ext != null)
+            mAvrcp_ext.storeVolumeForDevice(mActiveDevice);
 
         // Step 9: Clear active device and stop playing audio
         removeActiveDevice(true);
@@ -191,7 +274,7 @@
         mA2dpCodecConfig = null;
 
         // Step 4: Destroy state machines and stop handler thread
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             for (A2dpStateMachine sm : mStateMachines.values()) {
                 sm.doQuit();
                 sm.cleanup();
@@ -202,13 +285,30 @@
         mStateMachinesThread = null;
 
         // Step 3: Cleanup AVRCP
-        mAvrcp.doQuit();
-        mAvrcp.cleanup();
-        mAvrcp = null;
+        synchronized (mBtAvrcpLock) {
+            if(mAvrcp_ext != null) {
+                mAvrcp_ext.doQuit();
+                mAvrcp_ext.cleanup();
+                Avrcp_ext.clearAvrcpInstance();
+                mAvrcp_ext = null;
+            } else if(mAvrcp != null) {
+                mAvrcp.doQuit();
+                mAvrcp.cleanup();
+                mAvrcp = null;
+            }
+        }
 
         // Step 2: Reset maximum number of connected audio devices
-        mMaxConnectedAudioDevices = 1;
-
+        if (mAdapterService.isVendorIntfEnabled()) {
+            if (mIsTwsPlusEnabled) {
+                mMaxConnectedAudioDevices = 2;
+            } else {
+               mMaxConnectedAudioDevices = 1;
+            }
+        } else {
+            mMaxConnectedAudioDevices = 1;
+        }
+        mSetMaxConnectedAudioDevices = 1;
         // Step 1: Clear BluetoothAdapter, AdapterService, A2dpNativeInterface, AudioManager
         mAudioManager = null;
         mA2dpNativeInterface = null;
@@ -258,28 +358,49 @@
             return false;
         }
 
-        synchronized (mStateMachines) {
-            if (!connectionAllowedCheckMaxDevices(device)) {
+        synchronized (mBtA2dpLock) {
+            disconnectExisting = false;
+            if (!connectionAllowedCheckMaxDevices(device) && !disconnectExisting) {
                 Log.e(TAG, "Cannot connect to " + device + " : too many connected devices");
                 return false;
             }
+            if (disconnectExisting) {
+                disconnectExisting = false;
+                //Log.e(TAG,"Disconnect existing connections");
+                List <BluetoothDevice> connectingConnectedDevices =
+                      getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+                Log.e(TAG,"Disconnect existing connections = " + connectingConnectedDevices.size());
+                for (BluetoothDevice connectingConnectedDevice : connectingConnectedDevices) {
+                    Log.d(TAG,"calling disconnect to " + connectingConnectedDevice);
+                    disconnect(connectingConnectedDevice);
+                }
+            }
             A2dpStateMachine smConnect = getOrCreateStateMachine(device);
             if (smConnect == null) {
                 Log.e(TAG, "Cannot connect to " + device + " : no state machine");
                 return false;
             }
+            if (mA2dpSrcSnkConcurrency) {
+                sA2dpSinkService = A2dpSinkService.getA2dpSinkService();
+                List<BluetoothDevice> srcDevs = sA2dpSinkService.getConnectedDevices();
+                for ( BluetoothDevice src : srcDevs ) {
+                    Log.d(TAG, "calling sink disconnect to " + src);
+                    sA2dpSinkService.disconnect(src);
+                }
+            }
+
             smConnect.sendMessage(A2dpStateMachine.CONNECT);
             return true;
         }
     }
 
-    boolean disconnect(BluetoothDevice device) {
+    public boolean disconnect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "disconnect(): " + device);
         }
 
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             A2dpStateMachine sm = mStateMachines.get(device);
             if (sm == null) {
                 Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine");
@@ -292,7 +413,7 @@
 
     public List<BluetoothDevice> getConnectedDevices() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             List<BluetoothDevice> devices = new ArrayList<>();
             for (A2dpStateMachine sm : mStateMachines.values()) {
                 if (sm.isConnected()) {
@@ -302,7 +423,59 @@
             return devices;
         }
     }
+    private boolean isConnectionAllowed(BluetoothDevice device, boolean tws_connected,
+                                        int num_connected) {
+        if (!mIsTwsPlusEnabled && mAdapterService.isTwsPlusDevice(device)) {
+           Log.d(TAG, "No TWSPLUS connections as It is not Enabled");
+           return false;
+        }
+        if (num_connected == 0) return true;
 
+        List <BluetoothDevice> connectingConnectedDevices =
+                  getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+        BluetoothDevice mConnDev = null;
+        if(!connectingConnectedDevices.isEmpty())
+            mConnDev = connectingConnectedDevices.get(0);
+        if (mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTING ||
+            mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTED) {
+            if ((!mAdapterService.isTwsPlusDevice(device) && tws_connected) ||
+                (mAdapterService.isTwsPlusDevice(device) && !tws_connected)) {
+                Log.d(TAG,"isConnectionAllowed: incoming connection not allowed");
+                mA2dpStackEvent = EVENT_TYPE_NONE;
+                return false;
+            }
+        }
+        if (num_connected > 1 &&
+           ((!tws_connected && mAdapterService.isTwsPlusDevice(device)) ||
+           (tws_connected && !mAdapterService.isTwsPlusDevice(device)))) {
+            Log.d(TAG,"isConnectionAllowed: Max connections reached");
+            return false;
+        }
+        if (!tws_connected && mAdapterService.isTwsPlusDevice(device)) {
+            Log.d(TAG,"isConnectionAllowed: Disconnect legacy device for outgoing TWSP connection");
+            disconnectExisting = true;
+            return false;
+        }
+        if (tws_connected && mAdapterService.isTwsPlusDevice(device)) {
+            //if (num_connected == mMaxConnectedAudioDevices) {
+            if (num_connected > 1) {
+                Log.d(TAG,"isConnectionAllowed: Max TWS connected, disconnect first");
+                return false;
+            } else if(mConnDev != null && mAdapterService.getTwsPlusPeerAddress(mConnDev).equals(device.getAddress())) {
+                Log.d(TAG,"isConnectionAllowed: Peer earbud pair allow connection");
+                return true;
+            } else {
+                Log.d(TAG,"isConnectionAllowed: Unpaired earbud, disconnect previous TWS+ device");
+                disconnectExisting = true;
+                return false;
+            }
+        } else if (tws_connected && !mAdapterService.isTwsPlusDevice(device)) {
+            Log.d(TAG,"isConnectionAllowed: Disconnect tws device to connect to legacy headset");
+            disconnectExisting = true;
+            return false;
+        }
+        return false;
+    }
     /**
      * Check whether can connect to a peer device.
      * The check considers the maximum number of connected peers.
@@ -312,8 +485,9 @@
      */
     private boolean connectionAllowedCheckMaxDevices(BluetoothDevice device) {
         int connected = 0;
+        boolean tws_device = false;
         // Count devices that are in the process of connecting or already connected
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             for (A2dpStateMachine sm : mStateMachines.values()) {
                 switch (sm.getConnectionState()) {
                     case BluetoothProfile.STATE_CONNECTING:
@@ -321,6 +495,9 @@
                         if (Objects.equals(device, sm.getDevice())) {
                             return true;    // Already connected or accounted for
                         }
+                        if (tws_device == false) {
+                            tws_device = mAdapterService.isTwsPlusDevice(sm.getDevice());
+                        }
                         connected++;
                         break;
                     default:
@@ -328,6 +505,18 @@
                 }
             }
         }
+        Log.d(TAG,"connectionAllowedCheckMaxDevices connected = " + connected);
+        if (mAdapterService.isVendorIntfEnabled() &&
+            (tws_device || mAdapterService.isTwsPlusDevice(device) ||
+            (tws_device && connected == mMaxConnectedAudioDevices &&
+            !mAdapterService.isTwsPlusDevice(device)))) {
+            return isConnectionAllowed(device, tws_device, connected);
+        }
+        if (mSetMaxConnectedAudioDevices == 1 &&
+            connected == mSetMaxConnectedAudioDevices) {
+            disconnectExisting = true;
+            return true;
+        }
         return (connected < mMaxConnectedAudioDevices);
     }
 
@@ -354,6 +543,7 @@
                     + " : too many connected devices");
             return false;
         }
+
         // Check priority and accept or reject the connection.
         // Note: Logic can be simplified, but keeping it this way for readability
         int priority = getPriority(device);
@@ -382,7 +572,7 @@
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         List<BluetoothDevice> devices = new ArrayList<>();
         Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             for (BluetoothDevice device : bondedDevices) {
                 if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
                                                  BluetoothUuid.AudioSink)) {
@@ -411,7 +601,7 @@
     @VisibleForTesting
     List<BluetoothDevice> getDevices() {
         List<BluetoothDevice> devices = new ArrayList<>();
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             for (A2dpStateMachine sm : mStateMachines.values()) {
                 devices.add(sm.getDevice());
             }
@@ -421,7 +611,7 @@
 
     public int getConnectionState(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             A2dpStateMachine sm = mStateMachines.get(device);
             if (sm == null) {
                 return BluetoothProfile.STATE_DISCONNECTED;
@@ -432,7 +622,7 @@
 
     private void removeActiveDevice(boolean forceStopPlayingAudio) {
         BluetoothDevice previousActiveDevice = mActiveDevice;
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             // Clear the active device
             mActiveDevice = null;
             // This needs to happen before we inform the audio manager that the device
@@ -452,9 +642,28 @@
                     && (getConnectionState(previousActiveDevice)
                     == BluetoothProfile.STATE_CONNECTED);
             Log.i(TAG, "removeActiveDevice: suppressNoisyIntent=" + suppressNoisyIntent);
-            mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+
+            boolean isBAActive = false;
+            BATService mBatService = BATService.getBATService();
+            isBAActive = (mBatService != null) && (mBatService.isBATActive());
+            Log.d(TAG," removeActiveDevice: BA active " + isBAActive);
+            // If BA streaming is ongoing, we don't want to pause music player
+            if(isBAActive) {
+                suppressNoisyIntent = true;
+                Log.d(TAG," BA Active, suppress noisy intent");
+            }
+            if (mAdapterService.isTwsPlusDevice(previousActiveDevice) &&
+                mDummyDevice != null) {
+                previousActiveDevice = mDummyDevice;
+                mDummyDevice = null;
+            }
+            mDisconnectDelay =
+                    mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
                     previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
                     BluetoothProfile.A2DP, suppressNoisyIntent, -1);
+            if (mDisconnectDelay > 0) {
+                mDisconnectTime = SystemClock.uptimeMillis();
+            }
             // Make sure the Active device in native layer is set to null and audio is off
             if (!mA2dpNativeInterface.setActiveDevice(null)) {
                 Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native "
@@ -471,24 +680,42 @@
      */
     public boolean setActiveDevice(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-        synchronized (mStateMachines) {
-            BluetoothDevice previousActiveDevice = mActiveDevice;
-            if (DBG) {
-                Log.d(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice);
-            }
+        synchronized (mActiveDeviceLock) {
+            return setActiveDeviceInternal(device);
+        }
+    }
+    private boolean setActiveDeviceInternal(BluetoothDevice device) {
+        boolean deviceChanged;
+        BluetoothCodecStatus codecStatus = null;
+        BluetoothDevice previousActiveDevice = mActiveDevice;
+        boolean isBAActive = false;
+        Log.w(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice);
 
-            if (previousActiveDevice != null && AvrcpTargetService.get() != null) {
-                AvrcpTargetService.get().storeVolumeForDevice(previousActiveDevice);
-            }
+        if (previousActiveDevice != null && AvrcpTargetService.get() != null) {
+            AvrcpTargetService.get().storeVolumeForDevice(previousActiveDevice);
+        } else if (previousActiveDevice != null && mAvrcp_ext != null) {
+            //Store volume only if SHO is triggered or output device other than BT is selected
+            mAvrcp_ext.storeVolumeForDevice(previousActiveDevice);
+        }
+        synchronized (mBtA2dpLock) {
+            BATService mBatService = BATService.getBATService();
+            isBAActive = (mBatService != null) && (mBatService.isBATActive());
+            Log.d(TAG," setActiveDevice: BA active " + isBAActive);
 
             if (device == null) {
                 // Remove active device and continue playing audio only if necessary.
                 removeActiveDevice(false);
+                if(mAvrcp_ext != null)
+                    mAvrcp_ext.setActiveDevice(device);
                 return true;
             }
 
-            BluetoothCodecStatus codecStatus = null;
             A2dpStateMachine sm = mStateMachines.get(device);
+            deviceChanged = !Objects.equals(device, mActiveDevice);
+            if (!deviceChanged) {
+                Log.e(TAG, "setActiveDevice(" + device + "): already set to active ");
+                return true;
+            }
             if (sm == null) {
                 Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: "
                           + "no state machine");
@@ -499,58 +726,114 @@
                           + "device is not connected");
                 return false;
             }
+            if (mActiveDevice != null && mAdapterService.isTwsPlusDevice(device) &&
+                mAdapterService.isTwsPlusDevice(mActiveDevice) &&
+                !Objects.equals(device, mActiveDevice) &&
+                getConnectionState(mActiveDevice) == BluetoothProfile.STATE_CONNECTED) {
+                Log.d(TAG,"Ignore setActiveDevice request");
+                return false;
+            }
             if (!mA2dpNativeInterface.setActiveDevice(device)) {
                 Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer");
                 return false;
             }
             codecStatus = sm.getCodecStatus();
-
-            boolean deviceChanged = !Objects.equals(device, mActiveDevice);
             mActiveDevice = device;
             // This needs to happen before we inform the audio manager that the device
             // disconnected. Please see comment in broadcastActiveDevice() for why.
             broadcastActiveDevice(mActiveDevice);
-            if (deviceChanged) {
-                // Send an intent with the active device codec config
-                if (codecStatus != null) {
-                    broadcastCodecConfig(mActiveDevice, codecStatus);
+            Log.w(TAG, "setActiveDevice coming out of mutex lock");
+        }
+        if (deviceChanged &&
+            (mDummyDevice == null || !mAdapterService.isTwsPlusDevice(mActiveDevice))) {
+            if(mAvrcp_ext != null)
+                mAvrcp_ext.setActiveDevice(device);
+            if (mAdapterService.isTwsPlusDevice(device) && mDummyDevice == null) {
+                Log.d(TAG,"set dummy device for tws+");
+                mDummyDevice = mAdapter.getRemoteDevice("FA:CE:FA:CE:FA:CE");
+            }
+            // Send an intent with the active device codec config
+            if (codecStatus != null) {
+                broadcastCodecConfig(mActiveDevice, codecStatus);
+            }
+            int rememberedVolume = -1;
+            if (AvrcpTargetService.get() != null) {
+                AvrcpTargetService.get().volumeDeviceSwitched(device);
+
+                rememberedVolume = AvrcpTargetService.get()
+                        .getRememberedVolumeForDevice(device);
+            } else if (mAdapterService.isVendorIntfEnabled()) {
+                rememberedVolume = mAvrcp_ext.getVolume(device);
+                Log.d(TAG,"volume = " + rememberedVolume);
+            }
+            // Make sure the Audio Manager knows the previous Active device is disconnected,
+            // and the new Active device is connected.
+            // Also, mute and unmute the output during the switch to avoid audio glitches.
+            boolean wasMuted = false;
+            if (previousActiveDevice != null) {
+                if (!mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)) {
+                   mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+                                                 AudioManager.ADJUST_MUTE,
+                                                 mAudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+                   wasMuted = true;
                 }
-                // Make sure the Audio Manager knows the previous Active device is disconnected,
-                // and the new Active device is connected.
-                // Also, mute and unmute the output during the switch to avoid audio glitches.
-                boolean wasMuted = false;
-                if (previousActiveDevice != null) {
-                    if (!mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)) {
-                        mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
-                                                         AudioManager.ADJUST_MUTE, 0);
-                        wasMuted = true;
-                    }
+                if (mDummyDevice != null &&
+                    mAdapterService.isTwsPlusDevice(previousActiveDevice)) {
+                    mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                            mDummyDevice, BluetoothProfile.STATE_DISCONNECTED,
+                            BluetoothProfile.A2DP, true, -1);
+                    mDummyDevice = null;
+                } else  {
                     mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
                             previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
                             BluetoothProfile.A2DP, true, -1);
                 }
-
-                int rememberedVolume = -1;
-                if (AvrcpTargetService.get() != null) {
-                    AvrcpTargetService.get().volumeDeviceSwitched(mActiveDevice);
-
-                    rememberedVolume = AvrcpTargetService.get()
-                            .getRememberedVolumeForDevice(mActiveDevice);
+            }
+            // Check if ther is any delay set on audioservice for previous
+            // disconnect, if so then need to serialise disconnect/connect
+            // requests to audioservice, wait till prev disconnect is completed
+            if (mDisconnectDelay > 0) {
+                long currentTime = SystemClock.uptimeMillis();
+                if (mDisconnectDelay > (currentTime - mDisconnectTime)) {
+                    try {
+                        Log.d(TAG, "Enter wait for previous disconnect");
+                        Thread.sleep(mDisconnectDelay - (currentTime - mDisconnectTime));
+                        Log.d(TAG, "Exiting Wait for previous disconnect");
+                    } catch (InterruptedException e) {
+                        Log.e(TAG, "setactive was interrupted");
+                    }
                 }
+                mDisconnectDelay = 0;
+                mDisconnectTime = 0;
+            }
 
-                mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                        mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
-                        true, rememberedVolume);
-
-                // Inform the Audio Service about the codec configuration
-                // change, so the Audio Service can reset accordingly the audio
-                // feeding parameters in the Audio HAL to the Bluetooth stack.
-                mAudioManager.handleBluetoothA2dpDeviceConfigChange(mActiveDevice);
-                if (wasMuted) {
-                    mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
-                                                     AudioManager.ADJUST_UNMUTE, 0);
+            if (!isBAActive) {
+                if (mDummyDevice == null) {
+                    mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                            mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
+                            true, rememberedVolume);
+                } else {
+                    mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                            mDummyDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
+                            true, rememberedVolume);
                 }
             }
+
+            // Inform the Audio Service about the codec configuration
+            // change, so the Audio Service can reset accordingly the audio
+            // feeding parameters in the Audio HAL to the Bluetooth stack.
+            String offloadSupported =
+                 SystemProperties.get("persist.vendor.btstack.enable.splita2dp");
+            if (!(offloadSupported.isEmpty() || "true".equals(offloadSupported))) {
+                mAudioManager.handleBluetoothA2dpDeviceConfigChange(device);
+            }
+            if (wasMuted) {
+               mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+                                          AudioManager.ADJUST_UNMUTE,
+                                          mAudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+            }
+            if(mAvrcp_ext != null)
+                mAvrcp_ext.setAbsVolumeFlag(device);
         }
         return true;
     }
@@ -562,13 +845,13 @@
      */
     public BluetoothDevice getActiveDevice() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             return mActiveDevice;
         }
     }
 
     private boolean isActiveDevice(BluetoothDevice device) {
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             return (device != null) && Objects.equals(device, mActiveDevice);
         }
     }
@@ -593,7 +876,10 @@
 
     /* Absolute volume implementation */
     public boolean isAvrcpAbsoluteVolumeSupported() {
-        return mAvrcp.isAbsoluteVolumeSupported();
+        synchronized(mBtAvrcpLock) {
+            if (mAvrcp_ext != null) return mAvrcp_ext.isAbsoluteVolumeSupported();
+            return (mAvrcp != null) && mAvrcp.isAbsoluteVolumeSupported();
+        }
     }
 
     public void setAvrcpAbsoluteVolume(int volume) {
@@ -604,25 +890,53 @@
             return;
         }
 
-        mAvrcp.setAbsoluteVolume(volume);
-    }
-
-    public void setAvrcpAudioState(int state) {
-        mAvrcp.setA2dpAudioState(state);
-    }
-
-    public void resetAvrcpBlacklist(BluetoothDevice device) {
-        if (mAvrcp != null) {
-            mAvrcp.resetBlackList(device.getAddress());
+        synchronized(mBtAvrcpLock) {
+            if (mAvrcp_ext != null) {
+                mAvrcp_ext.setAbsoluteVolume(volume);
+                return;
+            }
+            if (mAvrcp != null) {
+                mAvrcp.setAbsoluteVolume(volume);
+            }
         }
     }
 
-    boolean isA2dpPlaying(BluetoothDevice device) {
+    public void setAvrcpAudioState(int state, BluetoothDevice device) {
+        synchronized(mBtAvrcpLock) {
+            if (mAvrcp_ext != null) {
+                mAvrcp_ext.setA2dpAudioState(state, device);
+            } else if (mAvrcp != null) {
+                mAvrcp.setA2dpAudioState(state);
+            }
+        }
+
+        if(state == BluetoothA2dp.STATE_NOT_PLAYING) {
+            GattService mGattService = GattService.getGattService();
+            if(mGattService != null) {
+                Log.d(TAG, "Enable BLE scanning");
+                mGattService.setAptXLowLatencyMode(false);
+            }
+        }
+    }
+
+    public void resetAvrcpBlacklist(BluetoothDevice device) {
+        synchronized(mBtAvrcpLock) {
+            if (mAvrcp_ext != null) {
+                mAvrcp_ext.resetBlackList(device.getAddress());
+                return;
+            }
+            if (mAvrcp != null) {
+                mAvrcp.resetBlackList(device.getAddress());
+            }
+        }
+    }
+
+    public boolean isA2dpPlaying(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (DBG) {
             Log.d(TAG, "isA2dpPlaying(" + device + ")");
         }
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             A2dpStateMachine sm = mStateMachines.get(device);
             if (sm == null) {
                 return false;
@@ -644,7 +958,7 @@
         if (DBG) {
             Log.d(TAG, "getCodecStatus(" + device + ")");
         }
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             if (device == null) {
                 device = mActiveDevice;
             }
@@ -681,6 +995,21 @@
             Log.e(TAG, "Cannot set codec config preference: no active A2DP device");
             return;
         }
+
+        if((codecConfig.getCodecSpecific4() & AptxBLEScanMask) > 0) {
+            GattService mGattService = GattService.getGattService();
+
+            if(mGattService != null) {
+                long mScanMode = codecConfig.getCodecSpecific4() & AptxBLEScanMask;
+                if(mScanMode == Aptx_BLEScanEnable) {
+                    mGattService.setAptXLowLatencyMode(false);
+                }
+                else if(mScanMode == Aptx_BLEScanDisable) {
+                    Log.w(TAG, "Disable BLE scanning to support aptX LL Mode");
+                    mGattService.setAptXLowLatencyMode(true);
+                }
+            }
+        }
         mA2dpCodecConfig.setCodecConfigPreference(device, codecConfig);
     }
 
@@ -769,7 +1098,7 @@
     void messageFromNative(A2dpStackEvent stackEvent) {
         Objects.requireNonNull(stackEvent.device,
                                "Device should never be null, event: " + stackEvent);
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             BluetoothDevice device = stackEvent.device;
             A2dpStateMachine sm = mStateMachines.get(device);
             if (sm == null) {
@@ -778,6 +1107,12 @@
                         case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
                         case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
                             // Create a new state machine only when connecting to a device
+                            if (mAdapterService.isVendorIntfEnabled())
+                                mA2dpStackEvent =  stackEvent.valueInt;
+                            if (mAdapterService.isTwsPlusDevice(device)) {
+                                sm = getOrCreateStateMachine(device);
+                                break;
+                            }
                             if (!connectionAllowedCheckMaxDevices(device)) {
                                 Log.e(TAG, "Cannot connect to " + device
                                         + " : too many connected devices");
@@ -786,14 +1121,40 @@
                             sm = getOrCreateStateMachine(device);
                             break;
                         default:
+                            if (mAdapterService.isVendorIntfEnabled() &&
+                                mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTED ||
+                                mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTING) {
+                                Log.d(TAG,"Reset local stack event value");
+                                mA2dpStackEvent = EVENT_TYPE_NONE;
+                            }
                             break;
                     }
                 }
+            } else {
+                if (mAdapterService.isVendorIntfEnabled()) {
+                    switch (sm.getConnectionState()) {
+                      case BluetoothProfile.STATE_DISCONNECTED:
+                        mA2dpStackEvent = stackEvent.valueInt;
+                        break;
+                      default:
+                        mA2dpStackEvent = EVENT_TYPE_NONE;
+                        break;
+                    }
+                }
             }
             if (sm == null) {
                 Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent);
                 return;
             }
+            if (mA2dpSrcSnkConcurrency &&
+                A2dpStackEvent.CONNECTION_STATE_CONNECTING == stackEvent.valueInt) {
+                sA2dpSinkService = A2dpSinkService.getA2dpSinkService();
+                List<BluetoothDevice> srcDevs = sA2dpSinkService.getConnectedDevices();
+                for ( BluetoothDevice src : srcDevs ) {
+                    Log.d(TAG, "calling sink disconnect to " + src);
+                    sA2dpSinkService.disconnect(src);
+                }
+            }
             sm.sendMessage(A2dpStateMachine.STACK_EVENT, stackEvent);
         }
     }
@@ -808,6 +1169,8 @@
      */
     void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
                             boolean sameAudioFeedingParameters) {
+        Log.w(TAG, "codecConfigUpdated for device:" + device +
+                                "sameAudioFeedingParameters: " + sameAudioFeedingParameters);
         broadcastCodecConfig(device, codecStatus);
 
         // Inform the Audio Service about the codec configuration change,
@@ -817,13 +1180,59 @@
             mAudioManager.handleBluetoothA2dpDeviceConfigChange(device);
         }
     }
+    void updateTwsChannelMode(int state, BluetoothDevice device) {
+       if (mIsTwsPlusMonoSupported) {
+         BluetoothDevice peerTwsDevice = mAdapterService.getTwsPlusPeerDevice(device);
+         Log.d(TAG, "TwsChannelMode: " + mTwsPlusChannelMode);
+         if ((state == BluetoothA2dp.STATE_PLAYING) && ("mono".equals(mTwsPlusChannelMode))) {
+             if ((peerTwsDevice!= null) && peerTwsDevice.isConnected() && isA2dpPlaying(peerTwsDevice)) {
+                 Log.d(TAG, "setparameters to Dual-Mono");
+                 mAudioManager.setParameters("TwsChannelConfig=dual-mono");
+                mTwsPlusChannelMode = "dual-mono";
+             }
+         } else if ("dual-mono".equals(mTwsPlusChannelMode)) {
+            if ((state == BluetoothA2dp.STATE_PLAYING) && (getConnectionState(peerTwsDevice) != BluetoothProfile.STATE_CONNECTED)) {
+               Log.d(TAG, "updateTwsChannelMode: send delay message ");
+               Message msg = mHandler.obtainMessage(SET_EBMONO_CFG);
+               mHandler.sendMessageDelayed(msg, MonoCfg_Timeout);
+            }
+            if ((state == BluetoothA2dp.STATE_PLAYING) && isA2dpPlaying(peerTwsDevice)) {
+               if (mHandler.hasMessages(SET_EBMONO_CFG)) {
+                 Log.d(TAG, "updateTwsChannelMode: remove delay message ");
+                 mHandler.removeMessages(SET_EBMONO_CFG);
+               }
+            }
+            if ((state == BluetoothA2dp.STATE_NOT_PLAYING) && isA2dpPlaying(peerTwsDevice)) {
+               Log.d(TAG, "setparameters to Mono");
+               mAudioManager.setParameters("TwsChannelConfig=mono");
+               mTwsPlusChannelMode = "mono";
+            }
+         }
+       } else {
+           Log.d(TAG,"TWS+ L/R to M feature not supported");
+       }
+    }
+
+    public void broadcastReconfigureA2dp() {
+        Log.w(TAG, "broadcastReconfigureA2dp(): set rcfg true to AudioManager");
+        boolean isBAActive = false;
+        BATService mBatService = BATService.getBATService();
+        isBAActive = (mBatService != null) && (mBatService.isBATActive());
+        Log.d(TAG," broadcastReconfigureA2dp: BA active " + isBAActive);
+        // If BA is active, don't inform AudioManager about reconfig.
+        if(isBAActive) {
+            return;
+        }
+        mAudioManager.setParameters("reconfigA2dp=true");
+    }
+
 
     private A2dpStateMachine getOrCreateStateMachine(BluetoothDevice device) {
         if (device == null) {
             Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
             return null;
         }
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             A2dpStateMachine sm = mStateMachines.get(device);
             if (sm != null) {
                 return sm;
@@ -900,7 +1309,7 @@
         if (bondState != BluetoothDevice.BOND_NONE) {
             return;
         }
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             A2dpStateMachine sm = mStateMachines.get(device);
             if (sm == null) {
                 return;
@@ -913,7 +1322,7 @@
     }
 
     private void removeStateMachine(BluetoothDevice device) {
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             A2dpStateMachine sm = mStateMachines.get(device);
             if (sm == null) {
                 Log.w(TAG, "removeStateMachine: device " + device
@@ -927,11 +1336,11 @@
         }
     }
 
-    private void updateOptionalCodecsSupport(BluetoothDevice device) {
+    public void updateOptionalCodecsSupport(BluetoothDevice device) {
         int previousSupport = getSupportsOptionalCodecs(device);
         boolean supportsOptional = false;
 
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             A2dpStateMachine sm = mStateMachines.get(device);
             if (sm == null) {
                 return;
@@ -965,21 +1374,12 @@
         if ((device == null) || (fromState == toState)) {
             return;
         }
-        synchronized (mStateMachines) {
+        synchronized (mBtA2dpLock) {
             if (toState == BluetoothProfile.STATE_CONNECTED) {
                 // Each time a device connects, we want to re-check if it supports optional
                 // codecs (perhaps it's had a firmware update, etc.) and save that state if
                 // it differs from what we had saved before.
                 updateOptionalCodecsSupport(device);
-                MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP);
-            }
-            // Set the active device if only one connected device is supported and it was connected
-            if (toState == BluetoothProfile.STATE_CONNECTED && (mMaxConnectedAudioDevices == 1)) {
-                setActiveDevice(device);
-            }
-            // Check if the active device is not connected anymore
-            if (isActiveDevice(device) && (fromState == BluetoothProfile.STATE_CONNECTED)) {
-                setActiveDevice(null);
             }
             // Check if the device is disconnected - if unbond, remove the state machine
             if (toState == BluetoothProfile.STATE_DISCONNECTED) {
@@ -1006,6 +1406,10 @@
                 return;
             }
             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            if (device.getAddress().equals(BATService.mBAAddress)) {
+                Log.d(TAG," ConnectionUpdate from BA, don't take action ");
+                return;
+            }
             int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
             int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
             connectionStateChanged(device, fromState, toState);
@@ -1215,11 +1619,19 @@
     public void dump(StringBuilder sb) {
         super.dump(sb);
         ProfileService.println(sb, "mActiveDevice: " + mActiveDevice);
-        for (A2dpStateMachine sm : mStateMachines.values()) {
-            sm.dump(sb);
+        synchronized(mBtA2dpLock) {
+            for (A2dpStateMachine sm : mStateMachines.values()) {
+                sm.dump(sb);
+            }
         }
-        if (mAvrcp != null) {
-            mAvrcp.dump(sb);
+        synchronized(mBtAvrcpLock) {
+            if (mAvrcp_ext != null) {
+                mAvrcp_ext.dump(sb);
+                return;
+            }
+            if (mAvrcp != null) {
+                mAvrcp.dump(sb);
+            }
         }
     }
 }
diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index 3ab26e1..fdc9a33 100644
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -64,6 +64,8 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Scanner;
+import android.os.SystemProperties;
+import com.android.bluetooth.btservice.AdapterService;
 
 final class A2dpStateMachine extends StateMachine {
     private static final boolean DBG = true;
@@ -130,7 +132,7 @@
             // Stop if auido is still playing
             log("doQuit: stopped playing " + mDevice);
             mIsPlaying = false;
-            mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
+            mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING, mDevice);
             broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
                                 BluetoothA2dp.STATE_PLAYING);
         }
@@ -148,8 +150,9 @@
             Message currentMessage = getCurrentMessage();
             Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + (currentMessage == null ? "null"
                     : messageWhatToString(currentMessage.what)));
-            mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
-
+            synchronized (this) {
+                mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+            }
             removeDeferredMessages(DISCONNECT);
 
             if (mLastConnectionState != -1) {
@@ -158,10 +161,15 @@
                 if (mIsPlaying) {
                     Log.i(TAG, "Disconnected: stopped playing: " + mDevice);
                     mIsPlaying = false;
-                    mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
+                    mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING, mDevice);
                     broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
                                         BluetoothA2dp.STATE_PLAYING);
                 }
+                AdapterService adapterService = AdapterService.getAdapterService();
+                if (adapterService.isVendorIntfEnabled() &&
+                     adapterService.isTwsPlusDevice(mDevice)) {
+                   mA2dpService.updateTwsChannelMode(BluetoothA2dp.STATE_NOT_PLAYING, mDevice);
+                }
             }
         }
 
@@ -264,7 +272,9 @@
             Log.i(TAG, "Enter Connecting(" + mDevice + "): " + (currentMessage == null ? "null"
                     : messageWhatToString(currentMessage.what)));
             sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
-            mConnectionState = BluetoothProfile.STATE_CONNECTING;
+            synchronized (this) {
+                mConnectionState = BluetoothProfile.STATE_CONNECTING;
+            }
             broadcastConnectionState(mConnectionState, mLastConnectionState);
         }
 
@@ -360,7 +370,9 @@
             Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null"
                     : messageWhatToString(currentMessage.what)));
             sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
-            mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+            synchronized (this) {
+                mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+            }
             broadcastConnectionState(mConnectionState, mLastConnectionState);
         }
 
@@ -464,8 +476,9 @@
             Message currentMessage = getCurrentMessage();
             Log.i(TAG, "Enter Connected(" + mDevice + "): " + (currentMessage == null ? "null"
                     : messageWhatToString(currentMessage.what)));
-            mConnectionState = BluetoothProfile.STATE_CONNECTED;
-
+            synchronized (this) {
+                mConnectionState = BluetoothProfile.STATE_CONNECTED;
+            }
             removeDeferredMessages(CONNECT);
 
             broadcastConnectionState(mConnectionState, mLastConnectionState);
@@ -559,9 +572,15 @@
                         if (!mIsPlaying) {
                             Log.i(TAG, "Connected: started playing: " + mDevice);
                             mIsPlaying = true;
-                            mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
+                            mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING, mDevice);
                             broadcastAudioState(BluetoothA2dp.STATE_PLAYING,
                                                 BluetoothA2dp.STATE_NOT_PLAYING);
+                            Log.i(TAG,"state:AUDIO_STATE_STARTED");
+                            AdapterService adapterService = AdapterService.getAdapterService();
+                            if (adapterService.isVendorIntfEnabled() &&
+                               adapterService.isTwsPlusDevice(mDevice)) {
+                               mA2dpService.updateTwsChannelMode(BluetoothA2dp.STATE_PLAYING, mDevice);
+                            }
                         }
                     }
                     break;
@@ -571,7 +590,7 @@
                         if (mIsPlaying) {
                             Log.i(TAG, "Connected: stopped playing: " + mDevice);
                             mIsPlaying = false;
-                            mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
+                            mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING, mDevice);
                             broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
                                                 BluetoothA2dp.STATE_PLAYING);
                         }
@@ -594,7 +613,7 @@
 
     boolean isConnected() {
         synchronized (this) {
-            return (getCurrentState() == mConnected);
+            return (mConnectionState == BluetoothProfile.STATE_CONNECTED);
         }
     }
 
@@ -613,13 +632,32 @@
     // NOTE: This event is processed in any state
     private void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
         BluetoothCodecConfig prevCodecConfig = null;
+        BluetoothCodecStatus prevCodecStatus = mCodecStatus;
+        int new_codec_type = newCodecStatus.getCodecConfig().getCodecType();
+        String offloadSupported =
+                SystemProperties.get("persist.vendor.btstack.enable.splita2dp");
+        if (DBG) Log.d(TAG, "START of A2dpService");
+        Log.w(TAG,"processCodecConfigEvent: new_codec_type = " + new_codec_type);
+        // Split A2dp will be enabled by default
+        if (offloadSupported.isEmpty() || "true".equals(offloadSupported)) {
+            if (new_codec_type  == BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX) {
+                AdapterService adapterService = AdapterService.getAdapterService();
+                if (adapterService.isVendorIntfEnabled() &&
+                    adapterService.isTwsPlusDevice(mDevice)) {
+                    Log.d(TAG,"TWSP device streaming,not calling reconfig");
+                    return;
+                }
+                mA2dpService.broadcastReconfigureA2dp();
+                Log.w(TAG,"Split A2dp enabled rcfg send to Audio for codec max");
+                return;
+            }
+        }
         synchronized (this) {
             if (mCodecStatus != null) {
                 prevCodecConfig = mCodecStatus.getCodecConfig();
             }
             mCodecStatus = newCodecStatus;
         }
-
         if (DBG) {
             Log.d(TAG, "A2DP Codec Config: " + prevCodecConfig + "->"
                     + newCodecStatus.getCodecConfig());
@@ -633,6 +671,14 @@
             }
         }
 
+        if (isConnected() && !sameSelectableCodec(prevCodecStatus, mCodecStatus)) {
+             // Remote selectable codec could be changed if codec config changed
+             // in connected state, we need to re-check optional codec status
+             // for this codec change event.
+             Log.d(TAG,"updating optional codec support as previous and current codec are different.");
+             mA2dpService.updateOptionalCodecsSupport(mDevice);
+        }
+
         if (mA2dpOffloadEnabled) {
             boolean update = false;
             BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
@@ -654,9 +700,28 @@
             return;
         }
 
-        boolean sameAudioFeedingParameters =
-                newCodecStatus.getCodecConfig().sameAudioFeedingParameters(prevCodecConfig);
-        mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, sameAudioFeedingParameters);
+        if (!(offloadSupported.isEmpty() || "true".equals(offloadSupported))) {
+            boolean isUpdateRequired = false;
+            if ((prevCodecConfig != null) && (prevCodecConfig.getCodecType() != new_codec_type)) {
+                Log.d(TAG, "previous codec is differs from new codec");
+                isUpdateRequired = true;
+            } else if (!newCodecStatus.getCodecConfig().sameAudioFeedingParameters(prevCodecConfig)) {
+                Log.d(TAG, "codec config parameters mismatched with previous config: ");
+                isUpdateRequired = true;
+            } else if ((newCodecStatus.getCodecConfig().getCodecType()
+                        == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC)
+                    && (prevCodecConfig != null)
+                    && (prevCodecConfig.getCodecSpecific1()
+                        != newCodecStatus.getCodecConfig().getCodecSpecific1())) {
+                Log.d(TAG, "LDAC: codec config parameters mismatched with previous config: ");
+                isUpdateRequired = true;
+            }
+            Log.d(TAG, "isUpdateRequired: " + isUpdateRequired);
+            //update MM only when previous and current codec config has been changed.
+            if (isUpdateRequired) {
+                mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, false);
+            }
+        }
     }
 
     // This method does not check for error conditon (newState == prevState)
@@ -715,6 +780,16 @@
         return Integer.toString(what);
     }
 
+    private static boolean sameSelectableCodec(BluetoothCodecStatus prevCodecStatus,
+                                      BluetoothCodecStatus newCodecStatus) {
+        if (prevCodecStatus == null) {
+            return false;
+        }
+        return BluetoothCodecStatus.sameCapabilities(
+                prevCodecStatus.getCodecsSelectableCapabilities(),
+                newCodecStatus.getCodecsSelectableCapabilities());
+    }
+
     private static String profileStateToString(int state) {
         switch (state) {
             case BluetoothProfile.STATE_DISCONNECTED:
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index 17c8885..a27f187 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -19,18 +19,34 @@
 import android.bluetooth.BluetoothAudioConfig;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothA2dpSink;
+import android.content.Context;
 import android.content.Intent;
+import android.media.AudioFormat;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.ParcelUuid;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.util.Log;
+import android.widget.Toast;
 
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.hfp.HeadsetService;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService;
 import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
 import com.android.bluetooth.btservice.ProfileService;
 
 import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 
 /**
  * Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application.
@@ -40,8 +56,26 @@
     private static final boolean DBG = true;
     private static final String TAG = "A2dpSinkService";
 
+    /* HashMap of A2dpSinkStateMachines for remote connected devices*/
+    private final ConcurrentMap<BluetoothDevice, A2dpSinkStateMachine> mStateMachines =
+            new ConcurrentHashMap<>();
+    private HandlerThread mStateMachinesThread;
+    private final Object mBtA2dpLock = new Object();
+    private AdapterService mAdapterService;
+
     private A2dpSinkStateMachine mStateMachine;
     private static A2dpSinkService sA2dpSinkService;
+    private static A2dpService sA2dpService;
+    private static HeadsetService sHeadsetService;
+    private static boolean mA2dpSrcSnkConcurrency;
+    protected static BluetoothDevice mStreamingDevice;
+
+    private static int mMaxA2dpSinkConnections = 1;
+    public static final int MAX_ALLOWED_SINK_CONNECTIONS = 2;
+
+    static {
+        classInitNative();
+    }
 
     @Override
     protected IProfileServiceBinder initBinder() {
@@ -53,10 +87,26 @@
         if (DBG) {
             Log.d(TAG, "start()");
         }
+
+        initNative();
+        mStateMachines.clear();
+        mStateMachinesThread = new HandlerThread("A2dpSinkService.StateMachines");
+        mStateMachinesThread.start();
+
+        mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
+                "AdapterService cannot be null when A2dpService starts");
+
+        mMaxA2dpSinkConnections = Math.min(
+                SystemProperties.getInt("persist.vendor.bt.a2dp.sink_conn", 1),
+                MAX_ALLOWED_SINK_CONNECTIONS);
+        mA2dpSrcSnkConcurrency= SystemProperties.getBoolean(
+                                "persist.vendor.service.bt.a2dp_concurrency", false);
+        if (DBG) {
+            Log.d(TAG, "A2DP concurrency set to " + mA2dpSrcSnkConcurrency);
+        }
         // Start the media browser service.
         Intent startIntent = new Intent(this, A2dpMediaBrowserService.class);
         startService(startIntent);
-        mStateMachine = A2dpSinkStateMachine.make(this, this);
         setA2dpSinkService(this);
         return true;
     }
@@ -67,9 +117,18 @@
             Log.d(TAG, "stop()");
         }
         setA2dpSinkService(null);
-        if (mStateMachine != null) {
-            mStateMachine.doQuit();
+
+        // Step 4: Destroy state machines and stop handler thread
+        synchronized (mBtA2dpLock) {
+            for (A2dpSinkStateMachine sm : mStateMachines.values()) {
+                sm.doQuit();
+                sm.cleanup();
+            }
+            mStateMachines.clear();
         }
+        mStateMachinesThread.quitSafely();
+        mStateMachinesThread = null;
+
         Intent stopIntent = new Intent(this, A2dpMediaBrowserService.class);
         stopService(stopIntent);
         return true;
@@ -77,9 +136,21 @@
 
     @Override
     protected void cleanup() {
-        if (mStateMachine != null) {
-            mStateMachine.cleanup();
-        }
+        cleanupNative();
+    }
+
+    protected void removeStateMachine(BluetoothDevice device) {
+        synchronized (mBtA2dpLock) {
+            A2dpSinkStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                Log.e(TAG, "State Machine not found for device:" + device);
+                return;
+            }
+            mStateMachines.remove(device);
+            sm.doQuit();
+            sm.cleanup();
+            sm = null;
+         }
     }
 
     //API Methods
@@ -106,45 +177,110 @@
     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;
-        }
-
+        if (DBG) Log.d(TAG, "connect(): " + device);
         if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
             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) {
+        if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
+                                         BluetoothUuid.AudioSource)) {
+            Log.e(TAG, "Cannot connect to " + device + " : Remote does not have A2DP Source UUID");
             return false;
         }
 
-        mStateMachine.sendMessage(A2dpSinkStateMachine.DISCONNECT, device);
+        A2dpSinkStateMachine sm = null;
+        synchronized (mBtA2dpLock) {
+            sm = getOrCreateStateMachine(device);
+            if (sm != null) {
+                int connectionState = sm.getConnectionState();
+                if (connectionState == BluetoothProfile.STATE_CONNECTED
+                        || connectionState == BluetoothProfile.STATE_CONNECTING) {
+                    Log.e(TAG, "Device (" + device + ") is already connected/connecting. Ignore");
+                    return false;
+                }
+            } else if (sm == null) {
+                return false;
+            }
+
+            if (mA2dpSrcSnkConcurrency) {
+                sA2dpService = A2dpService.getA2dpService();
+                List<BluetoothDevice> snkDevs = sA2dpService.getConnectedDevices();
+                for( BluetoothDevice snk : snkDevs ) {
+                    Log.d(TAG, "calling src disconnect to " + snk);
+                    sA2dpService.disconnect(snk);
+                }
+                sHeadsetService = HeadsetService.getHeadsetService();
+                List<BluetoothDevice> hsDevs = sHeadsetService.getConnectedDevices();
+                for ( BluetoothDevice hs : hsDevs ) {
+                    Log.d(TAG, "calling headset disconnect to " + hs);
+                    sHeadsetService.disconnect(hs);
+                }
+            }
+
+            sm.sendMessage(A2dpSinkStateMachine.CONNECT);
+        }
         return true;
     }
 
-    public List<BluetoothDevice> getConnectedDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getConnectedDevices();
+    public boolean disconnect(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+
+        if (DBG) {
+            Log.d(TAG, "disconnect(): " + device);
+        }
+
+        synchronized (mBtA2dpLock) {
+            A2dpSinkStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine");
+                return false;
+            }
+            // State check before Disconnect
+            int connectionState = sm.getConnectionState();
+            if (connectionState != BluetoothProfile.STATE_CONNECTED
+                    && connectionState != BluetoothProfile.STATE_CONNECTING) {
+                return false;
+            }
+            sm.sendMessage(A2dpSinkStateMachine.DISCONNECT);
+            return true;
+        }
     }
 
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getDevicesMatchingConnectionStates(states);
+        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
+        synchronized (mStateMachines) {
+            Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+            int connectionState;
+
+            for (BluetoothDevice device : bondedDevices) {
+                ParcelUuid[] featureUuids = device.getUuids();
+                if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSource)) {
+                    continue;
+                }
+                connectionState = BluetoothProfile.STATE_DISCONNECTED;
+                A2dpSinkStateMachine sm = mStateMachines.get(device);
+                if (sm != null) {
+                    connectionState = sm.getConnectionState();
+                }
+                for (int i = 0; i < states.length; i++) {
+                    if (connectionState == states[i]) {
+                        deviceList.add(device);
+                    }
+                }
+            }
+        }
+        return deviceList;
     }
 
-    int getConnectionState(BluetoothDevice device) {
+    public int getConnectionState(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getConnectionState(device);
+        synchronized (mBtA2dpLock) {
+            A2dpSinkStateMachine sm = mStateMachines.get(device);
+            if (sm != null) {
+                return sm.getConnectionState();
+            }
+        }
+        return BluetoothProfile.STATE_DISCONNECTED;
     }
 
     public boolean setPriority(BluetoothDevice device, int priority) {
@@ -174,6 +310,14 @@
      * component will take the focus away but also notify the stack to throw away incoming data.
      */
     public void informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
+        A2dpSinkStateMachine mStateMachine = null;
+        synchronized (mBtA2dpLock) {
+            mStateMachine = mStateMachines.get(device);
+            if (mStateMachine == null) {
+                Log.w(TAG, "state machine is not present for device:" + device);
+                return;
+            }
+        }
         if (mStateMachine != null) {
             if (keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PLAY
                     && keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
@@ -194,10 +338,25 @@
      * stopping playback.
      */
     public void informTGStatePlaying(BluetoothDevice device, boolean isPlaying) {
+        Log.d(TAG, "informTGStatePlaying: device: " + device
+                + ", mStreamingDevice:" + mStreamingDevice);
+        A2dpSinkStateMachine mStateMachine = null;
+        synchronized (mBtA2dpLock) {
+            mStateMachine = mStateMachines.get(device);
+            if (mStateMachine == null) {
+                return;
+            }
+        }
         if (mStateMachine != null) {
             if (!isPlaying) {
                 mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PAUSE);
             } else {
+                // Soft-Handoff from AVRCP Cmd (if received before AVDTP_START)
+                initiateHandoffOperations(device);
+                if (mStreamingDevice != null && !mStreamingDevice.equals(device)) {
+                    Log.d(TAG, "updating streaming device after avrcp status command");
+                    mStreamingDevice = device;
+                }
                 mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PLAY);
             }
         }
@@ -211,9 +370,14 @@
      * started from either the source or the sink endpoint.
      */
     public void requestAudioFocus(BluetoothDevice device, boolean request) {
-        if (mStateMachine != null) {
-            mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_REQUEST_FOCUS);
+        A2dpSinkStateMachine sm = null;
+        synchronized (mBtA2dpLock) {
+            sm = mStateMachines.get(device);
+            if (sm == null) {
+                return;
+            }
         }
+        sm.sendMessage(A2dpSinkStateMachine.EVENT_REQUEST_FOCUS);
     }
 
     synchronized boolean isA2dpPlaying(BluetoothDevice device) {
@@ -221,12 +385,25 @@
         if (DBG) {
             Log.d(TAG, "isA2dpPlaying(" + device + ")");
         }
+        synchronized (mBtA2dpLock) {
+            A2dpSinkStateMachine mStateMachine = mStateMachines.get(device);
+            if (mStateMachine == null) {
+                return false;
+            }
+        }
         return mStateMachine.isPlaying(device);
     }
 
     BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getAudioConfig(device);
+        A2dpSinkStateMachine sm = null;
+        synchronized (mBtA2dpLock) {
+            sm = mStateMachines.get(device);
+            if (sm == null) {
+                return null;
+            }
+        }
+        return sm.getAudioConfig(device);
     }
 
     //Binder object: Must be static class or memory leak may occur
@@ -346,4 +523,194 @@
             mStateMachine.dump(sb);
         }
     }
+
+    /* Get Number of connected/connecting devices*/
+    public List<BluetoothDevice> getConnectedDevices() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        synchronized (mBtA2dpLock) {
+            List<BluetoothDevice> devices = new ArrayList<>();
+            for (A2dpSinkStateMachine sm : mStateMachines.values()) {
+                if (sm.isConnected()) {
+                    devices.add(sm.getDevice());
+                }
+            }
+            return devices;
+        }
+    }
+
+    /* This API returns existing state machine for remote device or creates new if not present.*/
+    private A2dpSinkStateMachine getOrCreateStateMachine(BluetoothDevice device) {
+        if (device == null) {
+            Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
+            return null;
+        }
+        synchronized (mBtA2dpLock) {
+            A2dpSinkStateMachine sm = mStateMachines.get(device);
+            if (sm != null) {
+                Log.i(TAG, "Return existing state machine for device:" + device);
+                return sm;
+            }
+            // Limit the maximum number of state machines to avoid DoS
+            if (mStateMachines.size() >= mMaxA2dpSinkConnections) {
+                Log.e(TAG, "Maximum number of A2DP Sink Connections reached: "
+                        + mMaxA2dpSinkConnections);
+                return null;
+            }
+            if (DBG) {
+                Log.d(TAG, "Creating a new state machine for " + device);
+            }
+
+            sm = A2dpSinkStateMachine.make(this, mStateMachinesThread.getLooper(), device);
+            mStateMachines.put(device, sm);
+            return sm;
+        }
+    }
+
+    public static BluetoothDevice getCurrentStreamingDevice() {
+        return mStreamingDevice;
+    }
+
+    /* This API performs all the operations required for doing soft-Handoff */
+    public synchronized void initiateHandoffOperations(BluetoothDevice device) {
+        if (mStreamingDevice != null && !mStreamingDevice.equals(device)) {
+           Log.d(TAG, "Soft-Handoff. Prev Device:" + mStreamingDevice + ", New: " + device);
+
+           for (A2dpSinkStateMachine otherSm: mStateMachines.values()) {
+               BluetoothDevice otherDevice = otherSm.getDevice();
+               if (mStreamingDevice.equals(otherDevice)) {
+                   Log.d(TAG, "Release Audio Focus for " + otherDevice);
+                   otherSm.sendMessage(A2dpSinkStateMachine.EVENT_RELEASE_FOCUS);
+                   // Send Passthrough Command for PAUSE
+                   AvrcpControllerService avrcpService =
+                           AvrcpControllerService.getAvrcpControllerService();
+                   avrcpService.sendPassThroughCmd(mStreamingDevice,
+                       AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
+                       AvrcpControllerService.KEY_STATE_PRESSED);
+                   avrcpService.sendPassThroughCmd(mStreamingDevice,
+                       AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
+                       AvrcpControllerService.KEY_STATE_RELEASED);
+
+                   // send intent for updated streaming device so that media session is updated
+                   Intent intent = new Intent(A2dpMediaBrowserService.ACTION_DEVICE_UPDATED);
+                   intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+                   sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+
+                   /* set autoconnect priority of non-streaming device to PRIORITY_ON and priority
+                    *  of streaming device to PRIORITY_AUTO_CONNECT */
+                   setPriority(otherDevice, BluetoothProfile.PRIORITY_ON);
+                   setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
+                   break;
+               }
+           }
+       } else if (mStreamingDevice == null && device != null) {
+           Log.d(TAG, "Prev Device: Null. New Streaming Device: " + device);
+           // No Action Required
+       }
+    }
+
+    /* JNI Changes for SINK SHO */
+    private void onConnectionStateChanged(byte[] address, int state) {
+        BluetoothDevice device = getDevice(address);
+        Log.d(TAG, "onConnectionStateChanged. State = " + state + ", device:" + device
+                + ", streaming:" + mStreamingDevice);
+        A2dpSinkStateMachine sm = getOrCreateStateMachine(device);
+        if (sm == null || device == null) {
+            Log.e(TAG, "State Machine not found for device:" + device + ". Return.");
+            return;
+        }
+
+        // If streaming device is disconnected, release audio focus and update mStreamingDevice
+        if (state == BluetoothProfile.STATE_DISCONNECTED && device.equals(mStreamingDevice)) {
+            Log.d(TAG, "Release Audio Focus for Streaming device: " + device);
+            sm.sendMessage(A2dpSinkStateMachine.EVENT_RELEASE_FOCUS);
+            mStreamingDevice = null;
+        }
+
+        // Intiate Handoff operations when state has been connectiond
+        if (state == BluetoothProfile.STATE_CONNECTED) {
+            if (mStreamingDevice != null && !mStreamingDevice.equals(device)) {
+                Log.d(TAG, "current connected device: " + device + "is different from previous device");
+                initiateHandoffOperations(device);
+                mStreamingDevice = device;
+            } else if (device != null) {
+                mStreamingDevice = device;
+            }
+        }
+
+        A2dpSinkStateMachine.StackEvent event =
+                sm.new StackEvent(A2dpSinkStateMachine.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.device = device;
+        event.valueInt = state;
+        if (mA2dpSrcSnkConcurrency &&
+            state == BluetoothProfile.STATE_CONNECTING) {
+            sA2dpService = A2dpService.getA2dpService();
+            List<BluetoothDevice> snkDevs = sA2dpService.getConnectedDevices();
+            for ( BluetoothDevice snk : snkDevs ) {
+                Log.d(TAG, "calling src disconnect to " + snk);
+                sA2dpService.disconnect(snk);
+            }
+            sHeadsetService = HeadsetService.getHeadsetService();
+            List<BluetoothDevice> hsDevs = sHeadsetService.getConnectedDevices();
+            for ( BluetoothDevice hs : hsDevs ) {
+                Log.d(TAG, "calling headset disconnect to " + hs);
+                sHeadsetService.disconnect(hs);
+            }
+        }
+        sm.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+    }
+
+    private void onAudioStateChanged(byte[] address, int state) {
+        BluetoothDevice device = getDevice(address);
+        Log.d(TAG, "onAudioStateChanged. Audio State = " + state + ", device:" + device);
+        A2dpSinkStateMachine sm = mStateMachines.get(device);
+        if (sm == null) {
+            return;
+        }
+
+        // Intiate Handoff operations if AUDIO_STATE_STARTED for other connected device
+        if (state == A2dpSinkStateMachine.AUDIO_STATE_STARTED) {
+            initiateHandoffOperations(device);
+            mStreamingDevice = device; // mark playing device as streaming device
+        }
+
+        A2dpSinkStateMachine.StackEvent event =
+                sm.new StackEvent(A2dpSinkStateMachine.EVENT_TYPE_AUDIO_STATE_CHANGED);
+        event.device = device;
+        event.valueInt = state;
+        sm.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+    }
+
+    private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
+        BluetoothDevice device = getDevice(address);
+        Log.d(TAG, "onAudioConfigChanged:- device:" + device + " samplerate:" + sampleRate
+                + ", channelCount:" + channelCount);
+        A2dpSinkStateMachine sm = mStateMachines.get(device);
+        if (sm == null) {
+            return;
+        }
+
+        A2dpSinkStateMachine.StackEvent event =
+                sm.new StackEvent(A2dpSinkStateMachine.EVENT_TYPE_AUDIO_CONFIG_CHANGED);
+        event.device = device;
+        int channelConfig =
+                (channelCount == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO);
+        event.audioConfig =
+                new BluetoothAudioConfig(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
+        sm.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+    }
+
+    private static native void classInitNative();
+
+    private native void initNative();
+
+    private native void cleanupNative();
+
+    public static native boolean connectA2dpNative(byte[] address);
+
+    public static native boolean disconnectA2dpNative(byte[] address);
+
+    public static native void informAudioFocusStateNative(int focusGranted);
+
+    public static native void informAudioTrackGainNative(float focusGranted);
+
 }
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
old mode 100644
new mode 100755
index fb64318..fea7080
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -38,7 +38,9 @@
 import android.content.Intent;
 import android.media.AudioFormat;
 import android.media.AudioManager;
+import android.media.AudioSystem;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.PowerManager;
@@ -64,13 +66,14 @@
 
     static final int CONNECT = 1;
     static final int DISCONNECT = 2;
-    private static final int STACK_EVENT = 101;
+    protected static final int STACK_EVENT = 101;
     private static final int CONNECT_TIMEOUT = 201;
     public static final int EVENT_AVRCP_CT_PLAY = 301;
     public static final int EVENT_AVRCP_CT_PAUSE = 302;
     public static final int EVENT_AVRCP_TG_PLAY = 303;
     public static final int EVENT_AVRCP_TG_PAUSE = 304;
     public static final int EVENT_REQUEST_FOCUS = 305;
+    public static final int EVENT_RELEASE_FOCUS = 306;
 
     private static final int IS_INVALID_DEVICE = 0;
     private static final int IS_VALID_DEVICE = 1;
@@ -78,85 +81,71 @@
     public static final int AVRC_ID_PAUSE = 0x46;
     public static final int KEY_STATE_PRESSED = 0;
     public static final int KEY_STATE_RELEASED = 1;
+    private static final String BT_ADDR_KEY = "bt_addr";
 
     // Connection states.
     // 1. Disconnected: The connection does not exist.
     // 2. Pending: The connection is being established.
     // 3. Connected: The connection is established. The audio connection is in Idle state.
     private Disconnected mDisconnected;
-    private Pending mPending;
+    private Connecting mConnecting;
+    private Disconnecting mDisconnecting;
     private Connected mConnected;
 
     private A2dpSinkService mService;
     private Context mContext;
     private BluetoothAdapter mAdapter;
-    private IntentBroadcastHandler mIntentBroadcastHandler;
 
     private static final int MSG_CONNECTION_STATE_CHANGED = 0;
 
     private final Object mLockForPatch = new Object();
 
-    // 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 BluetoothDevice mPlayingDevice = null;
     private A2dpSinkStreamHandler mStreaming = null;
 
+    private final BluetoothDevice mDevice;
+    private static final String TAG = "A2dpSinkStateMachine";
+    private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+    private boolean mIsPlaying = false;
+    private Looper mLooper;
     private final HashMap<BluetoothDevice, BluetoothAudioConfig> mAudioConfigs =
             new HashMap<BluetoothDevice, BluetoothAudioConfig>();
+    private int mLastConnectionState = -1;
 
-    static {
-        classInitNative();
-    }
-
-    private A2dpSinkStateMachine(A2dpSinkService svc, Context context) {
-        super("A2dpSinkStateMachine");
+    private A2dpSinkStateMachine(A2dpSinkService svc, Looper looper, BluetoothDevice device) {
+        super(TAG, looper);
         mService = svc;
-        mContext = context;
+        mContext = svc;
+        mLooper = looper;
+        mDevice = device;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
 
-        initNative();
+        if (Looper.myLooper() == null)
+            Looper.prepare();
 
         mDisconnected = new Disconnected();
-        mPending = new Pending();
+        mConnecting = new Connecting();
         mConnected = new Connected();
+        mDisconnecting = new Disconnecting();
 
         addState(mDisconnected);
-        addState(mPending);
+        addState(mConnecting);
         addState(mConnected);
+        addState(mDisconnecting);
+
+        if (mAdapter != null) {
+            String bdAddr = mAdapter.getAddress();
+            AudioSystem.setParameters(BT_ADDR_KEY + "=" + bdAddr);
+            log("AudioSystem.setParameters, Key: " + BT_ADDR_KEY + " Value: " + bdAddr);
+        }
 
         setInitialState(mDisconnected);
 
-        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-
-        mIntentBroadcastHandler = new IntentBroadcastHandler();
+        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
     }
 
-    static A2dpSinkStateMachine make(A2dpSinkService svc, Context context) {
+    static A2dpSinkStateMachine make(A2dpSinkService svc, Looper looper, BluetoothDevice device) {
         Log.d("A2dpSinkStateMachine", "make");
-        A2dpSinkStateMachine a2dpSm = new A2dpSinkStateMachine(svc, context);
+        A2dpSinkStateMachine a2dpSm = new A2dpSinkStateMachine(svc, looper, device);
         a2dpSm.start();
         return a2dpSm;
     }
@@ -165,6 +154,11 @@
         if (DBG) {
             Log.d("A2dpSinkStateMachine", "Quit");
         }
+        if (mIsPlaying) {
+            mIsPlaying = false;
+            broadcastAudioState(mDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
+                                BluetoothA2dpSink.STATE_PLAYING);
+        }
         synchronized (A2dpSinkStateMachine.this) {
             mStreaming = null;
         }
@@ -172,47 +166,51 @@
     }
 
     public void cleanup() {
-        cleanupNative();
         mAudioConfigs.clear();
     }
 
     public void dump(StringBuilder sb) {
-        ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
-        ProfileService.println(sb, "mTargetDevice: " + mTargetDevice);
-        ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice);
+        ProfileService.println(sb, "mDevice: " + mDevice);
         ProfileService.println(sb, "StateMachine: " + this.toString());
+        ProfileService.println(sb, "isPlaying: " + mIsPlaying);
     }
 
     private class Disconnected extends State {
         @Override
         public void enter() {
-            log("Enter Disconnected: " + getCurrentMessage().what);
+            log("Enter Disconnected (Device: " + mDevice + ") : " + getCurrentMessage().what);
+            mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+            if (mIsPlaying) {
+                Log.i(TAG, "Disconnected: stopped playing: " + mDevice);
+                mIsPlaying = false;
+                broadcastAudioState(mDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
+                                BluetoothA2dpSink.STATE_PLAYING);
+            }
+            if (mLastConnectionState != -1) {
+                log("Quit State Machine for device:" + mDevice);
+                broadcastConnectionState(mDevice, mConnectionState, mLastConnectionState);
+                mService.removeStateMachine(mDevice);
+            }
         }
 
         @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;
-            }
+            log("Disconnected (Device: " + mDevice + ") Process Message: " + message.what);
 
             boolean retValue = HANDLED;
             switch (message.what) {
                 case CONNECT:
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                            BluetoothProfile.STATE_DISCONNECTED);
+                    if (mDevice == null) {
+                        Log.e(TAG, "State Machine for Null Device Reference, Return.");
+                        return NOT_HANDLED;
+                    }
 
-                    if (!connectA2dpNative(getByteAddress(device))) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTING);
+                    if (!A2dpSinkService.connectA2dpNative(getByteAddress(mDevice))) {
                         break;
                     }
 
                     synchronized (A2dpSinkStateMachine.this) {
-                        mTargetDevice = device;
-                        transitionTo(mPending);
+                        transitionTo(mConnecting);
                     }
                     // TODO(BT) remove CONNECT_TIMEOUT when the stack
                     //          sends back events consistently
@@ -243,44 +241,39 @@
 
         @Override
         public void exit() {
-            log("Exit Disconnected: " + getCurrentMessage().what);
+            log("Exit Disconnected: (" + mDevice + "): " + getCurrentMessage().what);
+            mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED;
         }
 
         // in Disconnected state
         private void processConnectionEvent(BluetoothDevice device, int state) {
             switch (state) {
                 case CONNECTION_STATE_DISCONNECTED:
-                    logw("Ignore A2DP DISCONNECTED event, device: " + device);
+                    logw("Device: " + device + " already in disconnected state. Ignore ");
                     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);
+                            transitionTo(mConnecting);
                         }
                     } else {
                         //reject the connection and stay in Disconnected state itself
                         logi("Incoming A2DP rejected");
-                        disconnectA2dpNative(getByteAddress(device));
+                        A2dpSinkService.disconnectA2dpNative(getByteAddress(device));
                     }
                     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));
+                        A2dpSinkService.disconnectA2dpNative(getByteAddress(device));
                     }
                     break;
                 case CONNECTION_STATE_DISCONNECTING:
@@ -293,36 +286,41 @@
         }
     }
 
-    private class Pending extends State {
+    private class Connecting extends State {
         @Override
         public void enter() {
-            log("Enter Pending: " + getCurrentMessage().what);
+            log("Enter CONNECTING: " + getCurrentMessage().what);
+            mConnectionState = BluetoothProfile.STATE_CONNECTING;
+            broadcastConnectionState(mDevice, mConnectionState, mLastConnectionState);
+        }
+
+        @Override
+        public void exit() {
+            log("Exit CONNECTING: " + getCurrentMessage().what);
+            mLastConnectionState = BluetoothProfile.STATE_CONNECTING;
         }
 
         @Override
         public boolean processMessage(Message message) {
-            log("Pending process message: " + message.what);
+            log("SM STATE: CONNECTING (" + mDevice + ") process message: " + message.what);
 
             boolean retValue = HANDLED;
             switch (message.what) {
                 case CONNECT:
-                    logd("Disconnect before connecting to another target");
+                    logd("Connection is already in pregress.");
                     break;
                 case CONNECT_TIMEOUT:
-                    onConnectionStateChanged(getByteAddress(mTargetDevice),
+                    onConnectionStateChanged(getByteAddress(mDevice),
                                              CONNECTION_STATE_DISCONNECTED);
                     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;
-                        }
+                    if (mDevice == null) {
+                        Log.e(TAG, "Message received for wrong device.");
+                        return NOT_HANDLED;
                     }
+                    // cancel connection to the mDevice
+                    A2dpSinkService.disconnectA2dpNative(getByteAddress(mDevice)); //
+                    transitionTo(mDisconnected);
                     break;
                 case STACK_EVENT:
                     StackEvent event = (StackEvent) message.obj;
@@ -349,134 +347,111 @@
         // in Pending state
         private void processConnectionEvent(BluetoothDevice device, int state) {
             log("processConnectionEvent state " + state);
-            log("Devices curr: " + mCurrentDevice + " target: " + mTargetDevice + " incoming: "
-                    + mIncomingDevice + " device: " + device);
             switch (state) {
                 case CONNECTION_STATE_DISCONNECTED:
+                    // connection failed
                     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);
+                    synchronized (A2dpSinkStateMachine.this) {
+                        transitionTo(mDisconnected);
                     }
                     break;
                 case CONNECTION_STATE_CONNECTED:
-                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
-                        loge("current device is not null");
-                        // 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)) {
-                        loge("target device is not null");
-                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
-                                BluetoothProfile.STATE_CONNECTING);
-                        synchronized (A2dpSinkStateMachine.this) {
-                            mCurrentDevice = mTargetDevice;
-                            mTargetDevice = null;
-                            transitionTo(mConnected);
-                        }
-                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
-                        loge("incoming device is not null");
-                        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 by connecting to
-                        // the new device and disconnect from previous device.
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
-                                BluetoothProfile.STATE_DISCONNECTED);
-                        broadcastConnectionState(mCurrentDevice,
-                                BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTING);
-                        synchronized (A2dpSinkStateMachine.this) {
-                            mCurrentDevice = device;
-                            mTargetDevice = null;
-                            mIncomingDevice = null;
-                            transitionTo(mConnected);
-                        }
+                    // Connection completed
+                    synchronized (A2dpSinkStateMachine.this) {
+                        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");
-                    }
+                    log("current device tries to connect back. 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);
+                    // remote trying to disconnect
+                    if (DBG) {
+                        log("stack is disconnecting mDevice");
+                    }
+                    transitionTo(mDisconnecting);
+                    break;
+                default:
+                    loge("Incorrect state: " + state);
+                    break;
+            }
+        }
+    }
+
+    private class Disconnecting extends State {
+        @Override
+        public void enter() {
+            log("Enter Disconnecting: " + getCurrentMessage().what);
+            mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+            broadcastConnectionState(mDevice, mConnectionState, mLastConnectionState);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            log("DISCONNECTING (" + mDevice + ") process message: " + message.what);
+
+            boolean retValue = HANDLED;
+            switch (message.what) {
+                case CONNECT:
+                    logd("Disconnection is in progress. Try again later.");
+                    break;
+                case CONNECT_TIMEOUT:
+                    onConnectionStateChanged(getByteAddress(mDevice),
+                                             CONNECTION_STATE_DISCONNECTED);
+                    break;
+                case DISCONNECT:
+                    Log.e(TAG, "Message received for wrong device.");
+                    break;
+
+                case EVENT_RELEASE_FOCUS:
+                    mStreaming.obtainMessage(A2dpSinkStreamHandler.RELEASE_FOCUS).sendToTarget();
+                    break;
+
+                case STACK_EVENT:
+                    StackEvent event = (StackEvent) message.obj;
+                    log("STACK_EVENT " + event.type);
+                    switch (event.type) {
+                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(event.device, event.valueInt);
+                            break;
+                        case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
+                            processAudioConfigEvent(event.device, event.audioConfig);
+                            break;
+                        default:
+                            loge("Unexpected stack event: " + event.type);
+                            break;
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return retValue;
+        }
+
+        // in Pending state
+        private void processConnectionEvent(BluetoothDevice device, int state) {
+            log("processConnectionEvent state " + state);
+            switch (state) {
+                case CONNECTION_STATE_DISCONNECTED:
+                    mAudioConfigs.remove(device);
+                    synchronized (A2dpSinkStateMachine.this) {
+                        transitionTo(mDisconnected);
+                    }
+                    break;
+                case CONNECTION_STATE_CONNECTED:
+                    // disconnection failed
+                    synchronized (A2dpSinkStateMachine.this) {
+                        transitionTo(mConnected);
+                    }
+                    break;
+                case CONNECTION_STATE_CONNECTING:
+                    log("Current device tries to connect back.");
+                    transitionTo(mConnecting);
+                    break;
+                case CONNECTION_STATE_DISCONNECTING:
+                    // we already broadcasted the intent, doing nothing here
+                    if (DBG) {
+                        log("stack is disconnecting mDevice. Ignore.");
                     }
                     break;
                 default:
@@ -485,56 +460,61 @@
             }
         }
 
+        @Override
+        public void exit() {
+            log("Exit Disconnecting: (" + mDevice + "): " + getCurrentMessage().what);
+            mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+        }
     }
 
     private class Connected extends State {
         @Override
         public void enter() {
-            log("Enter Connected: " + getCurrentMessage().what);
+            log("Enter Connected (Device: "+ mDevice + ") " + getCurrentMessage().what);
             // Upon connected, the audio starts out as stopped
-            broadcastAudioState(mCurrentDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
+            mConnectionState = BluetoothProfile.STATE_CONNECTED;
+            broadcastAudioState(mDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
                     BluetoothA2dpSink.STATE_PLAYING);
+            broadcastConnectionState(mDevice, mConnectionState, mLastConnectionState);
             synchronized (A2dpSinkStateMachine.this) {
                 if (mStreaming == null) {
                     if (DBG) {
                         log("Creating New A2dpSinkStreamHandler");
                     }
-                    mStreaming = new A2dpSinkStreamHandler(A2dpSinkStateMachine.this, mContext);
+                    mStreaming = new A2dpSinkStreamHandler(A2dpSinkStateMachine.this,
+                            mContext, mDevice);
                 }
             }
             if (mStreaming.getAudioFocus() == AudioManager.AUDIOFOCUS_NONE) {
-                informAudioFocusStateNative(0);
+                A2dpSinkService.informAudioFocusStateNative(0);
             }
         }
 
         @Override
+        public void exit() {
+            log("Exit Connected: (" + mDevice + "): " + getCurrentMessage().what);
+            mLastConnectionState = BluetoothProfile.STATE_CONNECTED;
+        }
+
+        @Override
         public boolean processMessage(Message message) {
-            log("Connected process message: " + message.what);
-            if (mCurrentDevice == null) {
-                loge("ERROR: mCurrentDevice is null in Connected");
+            log("Connected (Device: "+ mDevice + ") process message: " + message.what);
+            if (mDevice == null) {
+                loge("ERROR: Current Device is null in Connected");
                 return NOT_HANDLED;
             }
 
             switch (message.what) {
                 case CONNECT:
-                    logd("Disconnect before connecting to another target");
+                    logd("Connect received in Connected State");
                 break;
 
                 case DISCONNECT: {
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    if (!mCurrentDevice.equals(device)) {
+                    if (!A2dpSinkService.disconnectA2dpNative(getByteAddress(mDevice))) {
                         break;
                     }
-                    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
-                            BluetoothProfile.STATE_CONNECTED);
-                    if (!disconnectA2dpNative(getByteAddress(device))) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
-                                BluetoothProfile.STATE_DISCONNECTED);
-                        break;
-                    }
-                    mPlayingDevice = null;
                     mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT).sendToTarget();
-                    transitionTo(mPending);
+                    transitionTo(mDisconnecting);
                 }
                 break;
 
@@ -576,6 +556,10 @@
                     mStreaming.obtainMessage(A2dpSinkStreamHandler.REQUEST_FOCUS).sendToTarget();
                     break;
 
+                case EVENT_RELEASE_FOCUS:
+                    mStreaming.obtainMessage(A2dpSinkStreamHandler.RELEASE_FOCUS).sendToTarget();
+                    break;
+
                 default:
                     return NOT_HANDLED;
             }
@@ -584,21 +568,18 @@
 
         // in Connected state
         private void processConnectionEvent(BluetoothDevice device, int state) {
+            if (mDevice != null && device != null && !mDevice.equals(device)) {
+                Log.e(TAG, "Message received for wrong device.");
+                return;
+            }
             switch (state) {
                 case CONNECTION_STATE_DISCONNECTED:
                     mAudioConfigs.remove(device);
-                    if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
-                        mPlayingDevice = null;
-                    }
-                    if (mCurrentDevice.equals(device)) {
-                        broadcastConnectionState(mCurrentDevice,
-                                BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTED);
+                    if (mDevice.equals(device)) {
                         synchronized (A2dpSinkStateMachine.this) {
                             // Take care of existing audio focus in the streaming state machine.
                             mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT)
                                     .sendToTarget();
-                            mCurrentDevice = null;
                             transitionTo(mDisconnected);
                         }
                     } else {
@@ -612,19 +593,29 @@
         }
 
         private void processAudioStateEvent(BluetoothDevice device, int state) {
-            if (!mCurrentDevice.equals(device)) {
+            if (!mDevice.equals(device)) {
                 loge("Audio State Device:" + device + "is different from ConnectedDevice:"
-                        + mCurrentDevice);
+                        + mDevice);
                 return;
             }
             log(" processAudioStateEvent in state " + state);
             switch (state) {
                 case AUDIO_STATE_STARTED:
+                    mIsPlaying = true;
                     mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
+                    broadcastAudioState(device, BluetoothA2dpSink.STATE_PLAYING,
+                            BluetoothA2dpSink.STATE_NOT_PLAYING);
                     break;
                 case AUDIO_STATE_REMOTE_SUSPEND:
                 case AUDIO_STATE_STOPPED:
-                    mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
+                    mIsPlaying = false;
+                    if (mDevice.equals(A2dpSinkService.mStreamingDevice)) {
+                        mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
+                    } else {
+                        Log.d(TAG, "other than streaming device. Ignore");
+                    }
+                    broadcastAudioState(device, BluetoothA2dpSink.STATE_NOT_PLAYING,
+                            BluetoothA2dpSink.STATE_PLAYING);
                     break;
                 default:
                     loge("Audio State Device: " + device + " bad state: " + state);
@@ -639,36 +630,8 @@
         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;
-            }
-        }
+    int getConnectionState() {
+        return mConnectionState;
     }
 
     BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
@@ -679,7 +642,7 @@
         List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
         synchronized (this) {
             if (getCurrentState() == mConnected) {
-                devices.add(mCurrentDevice);
+                devices.add(mDevice);
             }
         }
         return devices;
@@ -687,13 +650,19 @@
 
     boolean isPlaying(BluetoothDevice device) {
         synchronized (this) {
-            if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
-                return true;
+            if ((mDevice != null) && (device.equals(mDevice))) {
+                return mIsPlaying;
             }
         }
         return false;
     }
 
+    boolean isConnected() {
+        synchronized (this) {
+            return (getCurrentState() == mConnected);
+        }
+    }
+
     // Utility Functions
     boolean okToConnect(BluetoothDevice device) {
         AdapterService adapterService = AdapterService.getAdapterService();
@@ -711,34 +680,17 @@
         return false;
     }
 
-    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 = 0;
-        mIntentBroadcastHandler.sendMessageDelayed(
-                mIntentBroadcastHandler.obtainMessage(MSG_CONNECTION_STATE_CHANGED, prevState,
-                        newState, device), delay);
+        if (prevState != newState && newState == BluetoothProfile.STATE_CONNECTED) {
+            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP_SINK);
+        }
+        Intent intent = new Intent(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+//FIXME            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+        Log.d(TAG, "Connection state " + device + ": " + prevState + "->" + newState);
     }
 
     private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
@@ -794,43 +746,17 @@
         return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
     }
 
-    private class StackEvent {
+    public class StackEvent {
         public int type = EVENT_TYPE_NONE;
         public BluetoothDevice device = null;
         public int valueInt = 0;
         public BluetoothAudioConfig audioConfig = null;
 
-        private StackEvent(int type) {
+        public 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) {
-            if (prevState != state && state == BluetoothProfile.STATE_CONNECTED) {
-                MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP_SINK);
-            }
-            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);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_CONNECTION_STATE_CHANGED:
-                    onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
-                    break;
-            }
-        }
-    }
-
     public boolean sendPassThruPlay(BluetoothDevice mDevice) {
         log("sendPassThruPlay + ");
         AvrcpControllerService avrcpCtrlService =
@@ -851,11 +777,15 @@
         }
     }
 
+    BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
     // Event types for STACK_EVENT message
-    private static final int EVENT_TYPE_NONE = 0;
-    private static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
-    private static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
-    private static final int EVENT_TYPE_AUDIO_CONFIG_CHANGED = 3;
+    protected static final int EVENT_TYPE_NONE = 0;
+    protected static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+    protected static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
+    protected static final int EVENT_TYPE_AUDIO_CONFIG_CHANGED = 3;
 
     // Do not modify without updating the HAL bt_av.h files.
 
@@ -870,17 +800,4 @@
     static final int AUDIO_STATE_STOPPED = 1;
     static final int AUDIO_STATE_STARTED = 2;
 
-    private static native void classInitNative();
-
-    private native void initNative();
-
-    private native void cleanupNative();
-
-    private native boolean connectA2dpNative(byte[] address);
-
-    private native boolean disconnectA2dpNative(byte[] address);
-
-    public native void informAudioFocusStateNative(int focusGranted);
-
-    public native void informAudioTrackGainNative(float focusGranted);
 }
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
index f442c49..5b96e13 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
@@ -29,6 +29,7 @@
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.avrcpcontroller.CoverArtUtils;
 
 import java.util.List;
 
@@ -51,7 +52,7 @@
  * restored.
  */
 public class A2dpSinkStreamHandler extends Handler {
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
     private static final String TAG = "A2dpSinkStreamHandler";
 
     // Configuration Variables
@@ -69,6 +70,7 @@
     public static final int AUDIO_FOCUS_CHANGE = 7; // Audio focus callback with associated change
     public static final int REQUEST_FOCUS = 8; // Request focus when the media service is active
     public static final int DELAYED_RESUME = 9; // If a call just ended allow stack time to settle
+    public static final int RELEASE_FOCUS = 10; // Release focus when requested
 
     // Used to indicate focus lost
     private static final int STATE_FOCUS_LOST = 0;
@@ -84,7 +86,7 @@
     private boolean mSentPause = false;
     // Keep track of the relevant audio focus (None, Transient, Gain)
     private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
-
+    private BluetoothDevice mDevice;
     // Focus changes when we are currently holding focus.
     private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
         @Override
@@ -97,18 +99,20 @@
         }
     };
 
-    public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context) {
+    public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context,
+            BluetoothDevice device) {
         mA2dpSinkSm = a2dpSinkSm;
         mContext = context;
+        mDevice = device;
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
     }
 
     @Override
     public void handleMessage(Message message) {
         if (DBG) {
-            Log.d(TAG, " process message: " + message.what);
-            Log.d(TAG, " audioFocus =  " + mAudioFocus);
+            Log.d(TAG, " process message: " + message.what + ", device:" + mDevice);
         }
+
         switch (message.what) {
             case SRC_STR_START:
                 mStreamAvailable = true;
@@ -120,10 +124,9 @@
                 }
                 // Audio stream has started, stop it if we don't have focus.
                 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
-                    sendAvrcpPause();
-                } else {
-                    startAvrcpUpdates();
+                    requestAudioFocus();
                 }
+                startAvrcpUpdates();
                 break;
 
             case SRC_STR_STOP:
@@ -155,12 +158,10 @@
                     startAvrcpUpdates();
                     break;
                 }
-                // Otherwise, pause if we don't have focus
                 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
-                    sendAvrcpPause();
-                } else {
-                    startAvrcpUpdates();
+                    requestAudioFocus();
                 }
+                startAvrcpUpdates();
                 break;
 
             case SRC_PAUSE:
@@ -174,6 +175,10 @@
                 }
                 break;
 
+            case RELEASE_FOCUS:
+                abandonAudioFocus();
+                break;
+
             case DISCONNECT:
                 // Remote device has disconnected, restore everything to default state.
                 stopAvrcpUpdates();
@@ -242,6 +247,7 @@
      * Utility functions.
      */
     private synchronized int requestAudioFocus() {
+        Log.d(TAG, "requestAudioFocus");
         // Bluetooth A2DP may carry Music, Audio Books, Navigation, or other sounds so mark content
         // type unknown.
         AudioAttributes streamAttributes =
@@ -257,6 +263,7 @@
                         .setOnAudioFocusChangeListener(mAudioFocusListener, this)
                         .build();
         int focusRequestStatus = mAudioManager.requestAudioFocus(focusRequest);
+        Log.d(TAG, "focusRequestStatus = " + focusRequestStatus);
         // If the request is granted begin streaming immediately and schedule an upgrade.
         if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
             startAvrcpUpdates();
@@ -269,21 +276,24 @@
 
     private synchronized void abandonAudioFocus() {
         stopFluorideStreaming();
-        mAudioManager.abandonAudioFocus(mAudioFocusListener);
-        mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
+        if (mAudioFocus != AudioManager.AUDIOFOCUS_NONE) {
+            Log.d(TAG, "abandoning audio focus");
+            mAudioManager.abandonAudioFocus(mAudioFocusListener);
+            mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
+        }
     }
 
     private void startFluorideStreaming() {
-        mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
-        mA2dpSinkSm.informAudioTrackGainNative(1.0f);
+        A2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
+        A2dpSinkService.informAudioTrackGainNative(1.0f);
     }
 
     private void stopFluorideStreaming() {
-        mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_LOST);
+        A2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_LOST);
     }
 
     private void setFluorideAudioTrackGain(float gain) {
-        mA2dpSinkSm.informAudioTrackGainNative(gain);
+        A2dpSinkService.informAudioTrackGainNative(gain);
     }
 
     private void startAvrcpUpdates() {
@@ -293,8 +303,8 @@
         if (DBG) {
             Log.d(TAG, "startAvrcpUpdates");
         }
-        if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
-            avrcpService.startAvrcpUpdates();
+        if (avrcpService != null && avrcpService.getConnectedDevices().size() >= 1) {
+            avrcpService.startAvrcpUpdates(mDevice);
         } else {
             Log.e(TAG, "startAvrcpUpdates failed because of connection.");
         }
@@ -307,11 +317,12 @@
         if (DBG) {
             Log.d(TAG, "stopAvrcpUpdates");
         }
-        if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
-            avrcpService.stopAvrcpUpdates();
+        if (avrcpService != null && avrcpService.getConnectedDevices().size() >= 1) {
+            avrcpService.stopAvrcpUpdates(mDevice);
         } else {
             Log.e(TAG, "stopAvrcpUpdates failed because of connection.");
         }
+
     }
 
     private void sendAvrcpPause() {
diff --git a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
index 739f17c..8e072ba 100644
--- a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
+++ b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
@@ -88,6 +88,8 @@
     private static final int MSG_DEVICE_BROWSE_DISCONNECT = 8;
     // Message sent when folder list is fetched.
     private static final int MSG_FOLDER_LIST = 9;
+    // Message sent when streaming device is updated after soft-handoff
+    private static final int MSG_DEVICE_UPDATED = 10;
 
     // Custom actions for PTS testing.
     private static final String CUSTOM_ACTION_VOL_UP =
@@ -113,6 +115,9 @@
     private long mTransportControlFlags = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY
             | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS;
 
+    public static final String ACTION_DEVICE_UPDATED =
+            "android.bluetooth.a2dp.mbs.action.DeviceUpdated";
+
     private static final class AvrcpCommandQueueHandler extends Handler {
         WeakReference<A2dpMediaBrowserService> mInst;
 
@@ -156,6 +161,8 @@
                 case MSG_FOLDER_LIST:
                     inst.msgFolderList((Intent) msg.obj);
                     break;
+                case MSG_DEVICE_UPDATED:
+                    inst.msgDeviceUpdated((BluetoothDevice) msg.obj);
                 default:
                     Log.e(TAG, "Message not handled " + msg);
             }
@@ -182,6 +189,7 @@
         filter.addAction(AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
         filter.addAction(AvrcpControllerService.ACTION_TRACK_EVENT);
         filter.addAction(AvrcpControllerService.ACTION_FOLDER_LIST);
+        filter.addAction(A2dpMediaBrowserService.ACTION_DEVICE_UPDATED);
         registerReceiver(mBtReceiver, filter);
 
         synchronized (this) {
@@ -301,7 +309,7 @@
                 mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
 
                 // Since we request explicit playback here we should start the updates to UI.
-                mAvrcpCtrlSrvc.startAvrcpUpdates();
+                mAvrcpCtrlSrvc.startAvrcpUpdates(mA2dpDevice);
             }
 
             // TRACK_EVENT should be fired eventually and the UI should be hence updated.
@@ -370,6 +378,8 @@
                         new Pair<PlaybackState, MediaMetadata>(pbb, mmd)).sendToTarget();
             } else if (AvrcpControllerService.ACTION_FOLDER_LIST.equals(action)) {
                 mAvrcpCommandQueue.obtainMessage(MSG_FOLDER_LIST, intent).sendToTarget();
+            } else if (ACTION_DEVICE_UPDATED.equals(action)) {
+                 mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_UPDATED, btDev).sendToTarget();
             }
         }
     };
@@ -386,6 +396,22 @@
         refreshInitialPlayingState();
     }
 
+    private synchronized void msgDeviceUpdated(BluetoothDevice device) {
+        if (device != null && device.equals(mA2dpDevice)) {
+            return;
+        }
+        Log.d(TAG, "msgDeviceUpdated. Previous: " + mA2dpDevice + " New: " + device);
+        // We are connected to a new device via A2DP now.
+        mA2dpDevice = device;
+        mAvrcpCtrlSrvc = AvrcpControllerService.getAvrcpControllerService();
+        if (mAvrcpCtrlSrvc == null) {
+            Log.e(TAG, "!!!AVRCP Controller cannot be null");
+            return;
+        }
+        if (mAvrcpCtrlSrvc.isBrowsingConnected(device))
+            notifyChildrenChanged("__ROOT__");
+        refreshInitialPlayingState();
+    }
 
     // Refresh the UI if we have a connected device and AVRCP is initialized.
     private synchronized void refreshInitialPlayingState() {
@@ -400,11 +426,10 @@
             return;
         }
 
-        if (mA2dpDevice != null && !mA2dpDevice.equals(devices.get(0))) {
+        if (mA2dpDevice != null && !devices.contains(mA2dpDevice)) {
             Log.w(TAG, "A2dp device : " + mA2dpDevice + " avrcp device " + devices.get(0));
             return;
         }
-        mA2dpDevice = devices.get(0);
         mA2dpSinkService = A2dpSinkService.getA2dpSinkService();
 
         PlaybackState playbackState = mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice);
diff --git a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
index 1d4810a..178ed07 100644
--- a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
+++ b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
@@ -42,7 +42,7 @@
 
 public class AddressedMediaPlayer {
     private static final String TAG = "AddressedMediaPlayer";
-    private static final Boolean DEBUG = false;
+    private static final Boolean DEBUG = true;
 
     private static final long SINGLE_QID = 1;
     private static final String UNKNOWN_TITLE = "(unknown)";
@@ -51,6 +51,7 @@
             "com.google.android.music.mediasession.music_metadata";
 
     private AvrcpMediaRspInterface mMediaInterface;
+    private Avrcp_ext mAvrcp = null;
     @NonNull private List<MediaSession.QueueItem> mNowPlayingList;
 
     private final List<MediaSession.QueueItem> mEmptyNowPlayingList;
@@ -64,6 +65,11 @@
         mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
     }
 
+    public AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface, Avrcp_ext mAvrcp_ext) {
+        this(mediaInterface);
+        mAvrcp = mAvrcp_ext;
+    }
+
     void cleanup() {
         if (DEBUG) {
             Log.v(TAG, "cleanup");
@@ -258,11 +264,44 @@
         Log.d(TAG, "sendTrackChangeWithId (" + type + "): controller " + mediaController);
         long qid = getActiveQueueItemId(mediaController);
         byte[] track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
+        Log.d(TAG, "qid: " + qid );
+        if ((type == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) &&
+            (qid != MediaSession.QueueItem.UNKNOWN_ID) &&
+            (qid != mLastTrackIdSent)) {
+             byte[] lastTrack =
+                    ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(mLastTrackIdSent).array();
+             mMediaInterface.trackChangedRsp(type, lastTrack);
+             type = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+        }
         // The nowPlayingList changed: the new list has the full data for the current item
+        Log.d(TAG, "last_sent_qid: " + mLastTrackIdSent);
         mMediaInterface.trackChangedRsp(type, track);
         mLastTrackIdSent = qid;
     }
 
+    void sendTrackChangeWithId(int type, @Nullable MediaController mediaController, byte[] bdaddr) {
+        Log.d(TAG, "sendTrackChangeWithId (" + type + "): controller " + mediaController);
+        if(mAvrcp == null) {
+            sendTrackChangeWithId(type, mediaController);
+            return;
+        }
+        long qid = getActiveQueueItemId(mediaController);
+        byte[] track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
+        Log.d(TAG, "qid: " + qid );
+        if ((type == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) &&
+            (qid != MediaSession.QueueItem.UNKNOWN_ID) &&
+            (qid != mLastTrackIdSent)) {
+             byte[] lastTrack =
+                    ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(mLastTrackIdSent).array();
+             mAvrcp.trackChangedAddressedRsp(type, lastTrack, bdaddr);
+             type = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+        }
+        // The nowPlayingList changed: the new list has the full data for the current item
+        Log.d(TAG, "last_sent_qid: " + mLastTrackIdSent);
+        mAvrcp.trackChangedAddressedRsp(type, track, bdaddr);
+        mLastTrackIdSent = qid;
+    }
+
     /*
      * helper method to check if startItem and endItem index is with range of
      * MediaItem list. (Resultset containing all items in current path)
@@ -436,6 +475,8 @@
                     } else {
                         attrValue = desc.getTitle().toString();
                     }
+                    if (attrValue == null)
+                        attrValue = "<Unknown Title>";
                     break;
 
                 case AvrcpConstants.ATTRID_ARTIST:
@@ -465,8 +506,8 @@
                     break;
 
                 case AvrcpConstants.ATTRID_COVER_ART:
-                    Log.e(TAG, "getAttrValue: Cover art attribute not supported");
-                    return null;
+                    attrValue = Avrcp_ext.getImgHandleFromTitle(desc.getTitle().toString());
+                    break;
 
                 default:
                     Log.e(TAG, "getAttrValue: Unknown attribute ID requested: " + attr);
@@ -482,7 +523,7 @@
             }
         }
         if (DEBUG) {
-            Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id:" + attr);
+            Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id: " + attr);
         }
         return attrValue;
     }
@@ -551,8 +592,7 @@
             return MediaSession.QueueItem.UNKNOWN_ID;
         }
         PlaybackState state = controller.getPlaybackState();
-        if (state == null || state.getState() == PlaybackState.STATE_BUFFERING
-                || state.getState() == PlaybackState.STATE_NONE) {
+        if (state == null || state.getState() == PlaybackState.STATE_NONE) {
             return MediaSession.QueueItem.UNKNOWN_ID;
         }
         long qid = state.getActiveQueueItemId();
diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
index e30ad41..2938582 100644
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ b/src/com/android/bluetooth/avrcp/Avrcp.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.graphics.Color;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -44,14 +45,21 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserManager;
 import android.util.Log;
 import android.view.KeyEvent;
 
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.AbstractionLayer;
 import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.ProfileService;
 
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.NotificationChannel;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -62,6 +70,11 @@
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
+import com.android.bluetooth.hfp.HeadsetService;
+
+import android.os.SystemProperties;
+import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
 
 /******************************************************************************
  * support Bluetooth AVRCP profile. support metadata, play status, event
@@ -69,9 +82,14 @@
  ******************************************************************************/
 
 public final class Avrcp {
-    private static final boolean DEBUG = false;
+    static final boolean DEBUG = true;
     private static final String TAG = "Avrcp";
     private static final String ABSOLUTE_VOLUME_BLACKLIST = "absolute_volume_blacklist";
+    private static final String AVRCP_VERSION_PROPERTY = "persist.bluetooth.avrcpversion";
+    private static final String AVRCP_1_4_STRING = "avrcp14";
+    private static final String AVRCP_1_5_STRING = "avrcp15";
+    private static final String AVRCP_1_6_STRING = "avrcp16";
+    private static final String AVRCP_NOTIFICATION_ID = "avrcp_notification";
 
     private Context mContext;
     private final AudioManager mAudioManager;
@@ -93,6 +111,7 @@
     private int mTrackChangedNT;
     private int mPlayPosChangedNT;
     private int mAddrPlayerChangedNT;
+    private int mAvailablePlayersChangedNT;
     private int mReportedPlayerID;
     private int mNowPlayingListChangedNT;
     private long mPlaybackIntervalMs;
@@ -103,11 +122,16 @@
     private int mRemoteVolume;
     private int mLastRemoteVolume;
     private int mInitialRemoteVolume;
+    private NotificationManager mNotificationManager;
 
     /* Local volume in audio index 0-15 */
     private int mLocalVolume;
     private int mLastLocalVolume;
     private int mAbsVolThreshold;
+    private int mLastPassthroughcmd;
+
+    private boolean mFastforward;
+    private boolean mRewind;
 
     private String mAddress;
     private HashMap<Integer, Integer> mVolumeMapping;
@@ -132,6 +156,7 @@
     public static final int BTRC_FEAT_METADATA = 0x01;
     public static final int BTRC_FEAT_ABSOLUTE_VOLUME = 0x02;
     public static final int BTRC_FEAT_BROWSE = 0x04;
+    public static final int BTRC_FEAT_AVRC_UI_UPDATE = 0x08;
 
     /* AVRC response codes, from avrc_defs */
     private static final int AVRC_RSP_NOT_IMPL = 8;
@@ -163,11 +188,13 @@
     private static final int MSG_ABS_VOL_TIMEOUT = 17;
     private static final int MSG_SET_A2DP_AUDIO_STATE = 18;
     private static final int MSG_NOW_PLAYING_CHANGED_RSP = 19;
+    private final static int MESSAGE_SET_MEDIA_SESSION = 24;
 
     private static final int CMD_TIMEOUT_DELAY = 2000;
     private static final int MAX_ERROR_RETRY_TIMES = 6;
     private static final int AVRCP_MAX_VOL = 127;
     private static final int AVRCP_BASE_VOLUME_STEP = 1;
+    private static final int SET_MEDIA_SESSION_DELAY = 300;
 
     /* Communicates with MediaPlayer to fetch media content */
     private BrowsedMediaPlayer mBrowsedMediaPlayer;
@@ -185,6 +212,8 @@
     /* Manage browsed players */
     private AvrcpBrowseManager mAvrcpBrowseManager;
 
+    /* BIP Responder */
+    static private AvrcpBipRsp mAvrcpBipRsp;
     /* Broadcast receiver for device connections intent broadcasts */
     private final BroadcastReceiver mAvrcpReceiver = new AvrcpServiceBroadcastReceiver();
     private final BroadcastReceiver mBootReceiver = new AvrcpServiceBootReceiver();
@@ -194,6 +223,7 @@
     private EvictingQueue<MediaKeyLog> mPassthroughLogs; // Passthorugh keys dispatched
     private List<MediaKeyLog> mPassthroughPending; // Passthrough keys sent not dispatched yet
     private int mPassthroughDispatched; // Number of keys dispatched
+    private boolean pts_test = false;
 
     private class MediaKeyLog {
         private long mTimeSent;
@@ -252,6 +282,7 @@
         mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+        mAvailablePlayersChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         mPlaybackIntervalMs = 0L;
         mLastReportedPosition = -1;
@@ -264,6 +295,9 @@
         mLastDirection = 0;
         mVolCmdSetInProgress = false;
         mAbsVolRetryTimes = 0;
+        mFastforward = false;
+        mRewind = false;
+        mLastPassthroughcmd = KeyEvent.KEYCODE_UNKNOWN;
         mLocalVolume = -1;
         mLastLocalVolume = -1;
         mAbsVolThreshold = 0;
@@ -274,7 +308,7 @@
         mContext = context;
         mLastUsedPlayerID = 0;
         mAddressedMediaPlayer = null;
-
+        mAvrcpBipRsp = null;
         initNative();
 
         mMediaSessionManager =
@@ -308,6 +342,20 @@
         IntentFilter bootFilter = new IntentFilter();
         bootFilter.addAction(Intent.ACTION_USER_UNLOCKED);
         context.registerReceiver(mBootReceiver, bootFilter);
+        pts_test = SystemProperties.getBoolean("vendor.bluetooth.avrcpct-passthrough.pts", false);
+
+
+        // create Notification channel.
+        mNotificationManager = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        NotificationChannel mChannel = new NotificationChannel(AVRCP_NOTIFICATION_ID,
+                mContext.getString(R.string.avrcp_notification_name),
+                NotificationManager.IMPORTANCE_DEFAULT);
+        mChannel.setDescription(mContext.getString(R.string.bluetooth_advanced_feat_description));
+        mChannel.enableLights(true);
+        mChannel.setLightColor(Color.GREEN);
+        mNotificationManager.createNotificationChannel(mChannel);
+
     }
 
     private synchronized void start() {
@@ -332,6 +380,27 @@
         }
         mPackageManager = mContext.getApplicationContext().getPackageManager();
 
+        boolean isCoverArtSupported = false;
+        AdapterService adapterService = AdapterService.getAdapterService();
+        if ((adapterService != null) && (adapterService.isVendorIntfEnabled())) {
+            if (adapterService.getProfileInfo(AbstractionLayer.AVRCP, AbstractionLayer.AVRCP_0103_SUPPORT))
+            {
+                isCoverArtSupported = false;
+            } else if(adapterService.getProfileInfo(AbstractionLayer.AVRCP, AbstractionLayer.AVRCP_COVERART_SUPPORT)){
+                isCoverArtSupported = true;
+            }
+        }
+        String avrcpVersion = SystemProperties.get(AVRCP_VERSION_PROPERTY, AVRCP_1_4_STRING);
+        if (DEBUG) Log.d(TAG, "avrcpVersion: " + avrcpVersion
+                + " isCoverArtSupported :"+isCoverArtSupported);
+        /* Enable Cover Art support is version is 1.6 and flag is set in config */
+        if (isCoverArtSupported && avrcpVersion != null &&
+            avrcpVersion.equals(AVRCP_1_6_STRING)) {
+            mAvrcpBipRsp = new AvrcpBipRsp(mContext);
+            mAvrcpBipRsp.start();
+            if (DEBUG) Log.d(TAG, "Starting AVRCP BIP Responder Service");
+        }
+
         /* create object to communicate with addressed player */
         mAddressedMediaPlayer = new AddressedMediaPlayer(mAvrcpMediaRsp);
 
@@ -384,13 +453,19 @@
         if (looper != null) {
             looper.quitSafely();
         }
-
+        if (mAvrcpBipRsp != null) {
+            mAvrcpBipRsp.stop();
+            mAvrcpBipRsp = null;
+        }
         mAudioManagerPlaybackHandler = null;
         mContext.unregisterReceiver(mAvrcpReceiver);
         mContext.unregisterReceiver(mBootReceiver);
 
         mAddressedMediaPlayer.cleanup();
         mAvrcpBrowseManager.cleanup();
+
+        if (mNotificationManager != null )
+            mNotificationManager.deleteNotificationChannel(AVRCP_NOTIFICATION_ID);
     }
 
     public void cleanup() {
@@ -498,10 +573,35 @@
                     mRemoteVolume = -1;
                     mLocalVolume = -1;
                     mInitialRemoteVolume = -1;
+                    mLastPassthroughcmd = KeyEvent.KEYCODE_UNKNOWN;
                     mAddress = address;
                     if (mVolumeMapping != null) {
                         mVolumeMapping.clear();
                     }
+
+                    Log.d(TAG, "avrcpct-passthrough pts_test = " + pts_test);
+                    if (pts_test) {
+                        Log.v(TAG,"fake BTRC_FEAT_ABSOLUTE_VOL remote feat support for pts test");
+                        mFeatures = mFeatures | BTRC_FEAT_ABSOLUTE_VOLUME;
+                    }
+
+                    if ((mFeatures & BTRC_FEAT_AVRC_UI_UPDATE) != 0) {
+                        int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
+                        Notification notification = new Notification.Builder(mContext)
+                            .setContentTitle(mContext.getString(R.string.bluetooth_rc_feat_title))
+                            .setContentText(mContext.getString(R.string.bluetooth_rc_feat_content))
+                            .setSubText(mContext.getString(R.string.bluetooth_rc_feat_subtext))
+                            .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
+                            .setChannelId(AVRCP_NOTIFICATION_ID)
+                            .setDefaults(Notification.DEFAULT_ALL)
+                            .build();
+
+                        if (mNotificationManager != null )
+                            mNotificationManager.notify(NOTIFICATION_ID, notification);
+                        else
+                            Log.e(TAG,"mNotificationManager is null");
+                        Log.v(TAG," update notification manager on remote repair request");
+                    }
                     break;
                 }
 
@@ -594,7 +694,10 @@
 
                     // convert remote volume to local volume
                     int volIndex = convertToAudioStreamVolume(absVol);
+                    boolean isShowUI = true;
                     if (mInitialRemoteVolume == -1) {
+                        //Don't show media UI when device connected.
+                        isShowUI = false;
                         mInitialRemoteVolume = absVol;
                         if (mAbsVolThreshold > 0 && mAbsVolThreshold < mAudioStreamMax
                                 && volIndex > mAbsVolThreshold) {
@@ -613,7 +716,11 @@
 
                     if (mLocalVolume != volIndex && (msg.arg2 == AVRC_RSP_ACCEPT
                             || msg.arg2 == AVRC_RSP_CHANGED || msg.arg2 == AVRC_RSP_INTERIM)) {
-                    /* If the volume has successfully changed */
+                        if (msg.arg2 == AVRC_RSP_ACCEPT){
+                            Log.d(TAG, "Don't show media UI when slide volume bar");
+                            isShowUI = false;
+                        }
+                        /* If the volume has successfully changed */
                         mLocalVolume = volIndex;
                         if (mLastLocalVolume != -1 && msg.arg2 == AVRC_RSP_ACCEPT) {
                             if (mLastLocalVolume != volIndex) {
@@ -628,7 +735,7 @@
                             }
                         }
 
-                        notifyVolumeChanged(mLocalVolume);
+                        notifyVolumeChanged(mLocalVolume, isShowUI);
                         mRemoteVolume = absVol;
                         long pecentVolChanged = ((long) absVol * 100) / 0x7f;
                         Log.e(TAG, "percent volume changed: " + pecentVolChanged + "%");
@@ -806,6 +913,12 @@
                     handlePassthroughCmd(msg.arg1, msg.arg2);
                     break;
 
+                case MESSAGE_SET_MEDIA_SESSION:
+                    android.media.session.MediaController mMediaController =
+                       (android.media.session.MediaController)msg.obj;
+                    setActiveMediaSession(mMediaController);
+                    break;
+
                 default:
                     Log.e(TAG, "unknown message! msg.what=" + msg.what);
                     break;
@@ -889,6 +1002,7 @@
         private String mMediaTotalNumber;
         private String mGenre;
         private long mPlayingTimeMs;
+        private String coverArt;
 
         private static final int ATTR_TITLE = 1;
         private static final int ATTR_ARTIST_NAME = 2;
@@ -897,6 +1011,7 @@
         private static final int ATTR_MEDIA_TOTAL_NUMBER = 5;
         private static final int ATTR_GENRE = 6;
         private static final int ATTR_PLAYING_TIME_MS = 7;
+        private static final int ATTR_COVER_ART = 8;
 
 
         MediaAttributes(MediaMetadata data) {
@@ -905,13 +1020,27 @@
                 return;
             }
 
+            String CurrentPackageName = (mMediaController != null) ?
+                                        mMediaController.getPackageName():null;
             mArtistName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ARTIST));
             mAlbumName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ALBUM));
-            mMediaNumber = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
+            if (CurrentPackageName != null && !(CurrentPackageName.equals("com.android.music"))) {
+                mMediaNumber =
+                    longStringOrBlank((data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER)));
+            } else {
+                /* playlist starts with 0 for default player*/
+                mMediaNumber =
+                    longStringOrBlank((data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER) + 1L));
+            }
             mMediaTotalNumber =
                     longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
             mGenre = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_GENRE));
             mPlayingTimeMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION);
+            if (mAvrcpBipRsp != null) {
+                coverArt = stringOrBlank(mAvrcpBipRsp.getImgHandle(mAlbumName));
+            } else {
+                coverArt = stringOrBlank(null);
+            }
 
             // Try harder for the title.
             mTitle = data.getString(MediaMetadata.METADATA_KEY_TITLE);
@@ -926,6 +1055,11 @@
                 }
             }
 
+            if (mTitle != null && CurrentPackageName != null &&
+                    CurrentPackageName.equals("com.tencent.qqmusic")) {
+                mTitle = mTitle.trim();
+            }
+
             if (mTitle == null) {
                 mTitle = new String();
             }
@@ -954,7 +1088,8 @@
             return (mTitle.equals(other.mTitle)) && (mArtistName.equals(other.mArtistName))
                     && (mAlbumName.equals(other.mAlbumName)) && (mMediaNumber.equals(
                     other.mMediaNumber)) && (mMediaTotalNumber.equals(other.mMediaTotalNumber))
-                    && (mGenre.equals(other.mGenre)) && (mPlayingTimeMs == other.mPlayingTimeMs);
+                    && (mGenre.equals(other.mGenre)) && (mPlayingTimeMs == other.mPlayingTimeMs)
+                    && (coverArt.equals(other.coverArt));
         }
 
         public String getString(int attrId) {
@@ -977,6 +1112,15 @@
                     return mGenre;
                 case ATTR_PLAYING_TIME_MS:
                     return Long.toString(mPlayingTimeMs);
+                case ATTR_COVER_ART:
+                    if (mAvrcpBipRsp != null) {
+                        /* Fetch coverArt Handle now in case OBEX channel is established just
+                        * before retrieving get element attribute. */
+                        coverArt = stringOrBlank(mAvrcpBipRsp.getImgHandle(mAlbumName));
+                    } else {
+                        coverArt = stringOrBlank(null);
+                    }
+                    return coverArt;
                 default:
                     return new String();
             }
@@ -998,7 +1142,7 @@
 
             return "[MediaAttributes: " + mTitle + " - " + mAlbumName + " by " + mArtistName + " ("
                     + mPlayingTimeMs + " " + mMediaNumber + "/" + mMediaTotalNumber + ") " + mGenre
-                    + "]";
+                    + "- " + coverArt + "]";
         }
 
         public String toRedactedString() {
@@ -1027,8 +1171,8 @@
 
         byte newPlayStatus = getBluetoothPlayState(newState);
 
-        if (newState.getState() != PlaybackState.STATE_BUFFERING
-                && newState.getState() != PlaybackState.STATE_NONE) {
+        if (newState != null && newState.getState() != PlaybackState.STATE_BUFFERING
+                 && newState.getState() != PlaybackState.STATE_NONE) {
             long newQueueId = MediaSession.QueueItem.UNKNOWN_ID;
             if (newState != null) {
                 newQueueId = newState.getActiveQueueItemId();
@@ -1040,18 +1184,27 @@
             }
 
             if (mAvailablePlayerViewChanged) {
-                registerNotificationRspAvalPlayerChangedNative(
-                        AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
+                Log.v(TAG, "Sending response for available playerchanged:");
+                if (mAvailablePlayersChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
+                    registerNotificationRspAvalPlayerChangedNative(
+                            AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
+                    mAvailablePlayersChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+                }
                 mAvailablePlayerViewChanged = false;
                 return;
             }
 
-            if (mAddrPlayerChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
-                    && mReportedPlayerID != mCurrAddrPlayerID) {
-                registerNotificationRspAvalPlayerChangedNative(
-                        AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
-                registerNotificationRspAddrPlayerChangedNative(
-                        AvrcpConstants.NOTIFICATION_TYPE_CHANGED, mCurrAddrPlayerID, sUIDCounter);
+            if (mReportedPlayerID != mCurrAddrPlayerID) {
+                if (mAvailablePlayersChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
+                    registerNotificationRspAvalPlayerChangedNative(
+                            AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
+                    mAvailablePlayersChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+                }
+                if (mAddrPlayerChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
+                    registerNotificationRspAddrPlayerChangedNative(
+                            AvrcpConstants.NOTIFICATION_TYPE_CHANGED,
+                            mCurrAddrPlayerID, sUIDCounter);
+                }
 
                 mAvailablePlayerViewChanged = false;
                 mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
@@ -1078,8 +1231,7 @@
             //  - Queue ID is valid and different from last Queue ID sent
             if ((newQueueId == -1 || newQueueId != mLastQueueId)
                     && mTrackChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
-                    && !currentAttributes.equals(mMediaAttributes)
-                    && newPlayStatus == PLAYSTATUS_PLAYING) {
+                    && !currentAttributes.equals(mMediaAttributes)) {
                 Log.v(TAG, "Send track changed");
                 mMediaAttributes = currentAttributes;
                 mLastQueueId = newQueueId;
@@ -1097,6 +1249,7 @@
         if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM || (mReportedPlayStatus
                 != newPlayStatus)) {
             sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_CHANGED, newPlayStatus);
+            mLastPassthroughcmd = KeyEvent.KEYCODE_UNKNOWN;
         }
 
         sendPlayPosNotificationRsp(false);
@@ -1162,8 +1315,22 @@
                 break;
 
             case EVT_PLAY_POS_CHANGED:
+                if (param <= 0)
+                    param = 1;
+
+                boolean isSplitA2dpEnabled = false;
+                long update_interval = 0L;
+                String offloadSupported = SystemProperties.get("persist.vendor.btstack.enable.splita2dp");
+                if (offloadSupported.isEmpty() || "true".equals(offloadSupported)) {
+                    isSplitA2dpEnabled = true;
+                    Log.v(TAG,"split enabled");
+                }
+                update_interval = (isSplitA2dpEnabled) ?
+                        SystemProperties.getLong("persist.vendor.btstack.avrcp.pos_time", 3000L):
+                        SystemProperties.getLong("persist.vendor.btstack.avrcp.pos_time", 1000L);
+
                 mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
-                mPlaybackIntervalMs = (long) param * 1000L;
+                mPlaybackIntervalMs = Math.max((long)param * 1000L, update_interval);
                 sendPlayPosNotificationRsp(true);
                 break;
 
@@ -1174,6 +1341,7 @@
                 }
                 registerNotificationRspAvalPlayerChangedNative(
                         AvrcpConstants.NOTIFICATION_TYPE_INTERIM);
+                mAvailablePlayersChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
                 break;
 
             case EVT_ADDR_PLAYER_CHANGED:
@@ -1221,6 +1389,9 @@
         }
 
         Message msg = handler.obtainMessage(MSG_NATIVE_REQ_PASS_THROUGH, id, keyState);
+        Bundle data = new Bundle();
+        data.putByteArray("BdAddress", address);
+        msg.setData(data);
         handler.sendMessage(msg);
     }
 
@@ -1239,7 +1410,8 @@
 
         MediaPlayerInfo info = getAddressedPlayerInfo();
         // for non-browsable players or no player
-        if (info != null && !info.isBrowseSupported()) {
+        if ((info != null && !info.isBrowseSupported()) ||
+                (mFeatures & BTRC_FEAT_BROWSE) == 0) {
             byte[] track = AvrcpConstants.TRACK_IS_SELECTED;
             if (!mMediaAttributes.mExists) {
                 track = AvrcpConstants.NO_TRACK_SELECTED;
@@ -1252,12 +1424,15 @@
     }
 
     private long getPlayPosition() {
+        long currPosition;
         if (mCurrentPlayState == null) {
+            Log.d(TAG, "getPlayPosition, mCurrentPlayState is null");
             return -1L;
         }
 
         if (mCurrentPlayState.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
-            return -1L;
+            Log.d(TAG, "getPlayPosition, currentPosition is unknown");
+            return (isPlayingState(mCurrentPlayState)) ? 0L : -1L;
         }
 
         if (isPlayingState(mCurrentPlayState)) {
@@ -1265,8 +1440,11 @@
                     (SystemClock.elapsedRealtime() - mCurrentPlayState.getLastPositionUpdateTime());
             return sinceUpdate + mCurrentPlayState.getPosition();
         }
-
-        return mCurrentPlayState.getPosition();
+        currPosition = mCurrentPlayState.getPosition();
+        if (mMediaAttributes.mPlayingTimeMs >= 0 && currPosition > mMediaAttributes.mPlayingTimeMs) {
+            currPosition = mMediaAttributes.mPlayingTimeMs;
+        }
+        return currPosition;
     }
 
     private boolean isPlayingState(@Nullable PlaybackState state) {
@@ -1312,9 +1490,8 @@
             }
             debugLine += " State: " + mCurrentPlayState.getState();
         }
-        if (requested || (
-                (mLastReportedPosition != playPositionMs) && (playPositionMs >= mNextPosMs) || (
-                        playPositionMs <= mPrevPosMs))) {
+        if (requested || ((mLastReportedPosition != playPositionMs) &&
+                    ((playPositionMs >= mNextPosMs) || (playPositionMs <= mPrevPosMs)))) {
             if (!requested) {
                 mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
             }
@@ -1375,6 +1552,7 @@
 
         Message msg = handler.obtainMessage(MSG_SET_ABSOLUTE_VOLUME, volume, 0);
         handler.sendMessage(msg);
+        Log.v(TAG, "Exit setAbsoluteVolume");
     }
 
     /* Called in the native layer as a btrc_callback to return the volume set on the carkit in the
@@ -1514,9 +1692,14 @@
         handler.sendMessage(msg);
     }
 
-    private void notifyVolumeChanged(int volume) {
-        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
-                AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+    private void notifyVolumeChanged(int volume, boolean isShowUI) {
+        if (isShowUI) {
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
+                    AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+        } else {
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
+                    AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+        }
     }
 
     private int convertToAudioStreamVolume(int volume) {
@@ -1712,36 +1895,43 @@
     private void setAddressedPlayer(byte[] bdaddr, int selectedId) {
         String functionTag = "setAddressedPlayer(" + selectedId + "): ";
 
-        synchronized (mMediaPlayerInfoList) {
-            if (mMediaPlayerInfoList.isEmpty()) {
-                Log.w(TAG, functionTag + "no players, send no available players");
-                setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_AVBL_PLAY);
-                return;
-            }
-            if (!mMediaPlayerInfoList.containsKey(selectedId)) {
-                Log.w(TAG, functionTag + "invalid id, sending response back ");
-                setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_INV_PLAYER);
-                return;
-            }
+        synchronized (this) {
+            synchronized (mMediaPlayerInfoList) {
+                if (mMediaPlayerInfoList.isEmpty()) {
+                    Log.w(TAG, functionTag + "no players, send no available players");
+                    setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_AVBL_PLAY);
+                    return;
+                }
+                if (selectedId == NO_PLAYER_ID) {
+                    Log.w(TAG, functionTag + "Respond dummy pass response ");
+                    setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR);
+                    return;
+                }
+                if (!mMediaPlayerInfoList.containsKey(selectedId)) {
+                    Log.w(TAG, functionTag + "invalid id, sending response back ");
+                    setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_INV_PLAYER);
+                    return;
+                }
 
-            if (isPlayerAlreadyAddressed(selectedId)) {
+                if (isPlayerAlreadyAddressed(selectedId)) {
+                    MediaPlayerInfo info = getAddressedPlayerInfo();
+                    Log.i(TAG, functionTag + "player already addressed: " + info);
+                    setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR);
+                    return;
+                }
+                // register new Media Controller Callback and update the current IDs
+                if (!updateCurrentController(selectedId, mCurrBrowsePlayerID)) {
+                    Log.e(TAG, functionTag + "updateCurrentController failed!");
+                    setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
+                    return;
+                }
+                // If we don't have a controller, try to launch the player
                 MediaPlayerInfo info = getAddressedPlayerInfo();
-                Log.i(TAG, functionTag + "player already addressed: " + info);
-                setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR);
-                return;
-            }
-            // register new Media Controller Callback and update the current IDs
-            if (!updateCurrentController(selectedId, mCurrBrowsePlayerID)) {
-                Log.e(TAG, functionTag + "updateCurrentController failed!");
-                setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
-                return;
-            }
-            // If we don't have a controller, try to launch the player
-            MediaPlayerInfo info = getAddressedPlayerInfo();
-            if (info.getMediaController() == null) {
-                Intent launch = mPackageManager.getLaunchIntentForPackage(info.getPackageName());
-                Log.i(TAG, functionTag + "launching player " + launch);
-                mContext.startActivity(launch);
+                if (info.getMediaController() == null) {
+                    Intent launch = mPackageManager.getLaunchIntentForPackage(info.getPackageName());
+                    Log.i(TAG, functionTag + "launching player " + launch);
+                    mContext.startActivity(launch);
+                }
             }
         }
         setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR);
@@ -1795,19 +1985,28 @@
                 @Override
                 public void onActiveSessionsChanged(
                         List<android.media.session.MediaController> newControllers) {
+                    if (newControllers.size() > 0) {
+                        HeadsetService mService = HeadsetService.getHeadsetService();
+                        if (mService != null && mService.isScoOrCallActive()) {
+                            Log.d(TAG, "Ignoring session changed update because of MT call in progress");
+                            return;
+                        }
+                    }
                     Set<String> updatedPackages = new HashSet<String>();
                     // Update the current players
-                    for (android.media.session.MediaController controller : newControllers) {
-                        String packageName = controller.getPackageName();
-                        if (DEBUG) {
-                            Log.v(TAG, "ActiveSession: " + MediaControllerFactory.wrap(controller));
+                    synchronized (Avrcp.this) {
+                        for (android.media.session.MediaController controller : newControllers) {
+                            String packageName = controller.getPackageName();
+                            if (DEBUG) {
+                                Log.v(TAG, "ActiveSession: " + MediaControllerFactory.wrap(controller));
+                            }
+                            // Only use the first (highest priority) controller from each package
+                            if (updatedPackages.contains(packageName)) {
+                                continue;
+                            }
+                            addMediaPlayerController(controller);
+                            updatedPackages.add(packageName);
                         }
-                        // Only use the first (highest priority) controller from each package
-                        if (updatedPackages.contains(packageName)) {
-                            continue;
-                        }
-                        addMediaPlayerController(controller);
-                        updatedPackages.add(packageName);
                     }
 
                     if (newControllers.size() > 0 && getAddressedPlayerInfo() == null) {
@@ -1842,16 +2041,19 @@
             addMediaPlayerPackage(packageName);
             updateCurrentMediaState();
         }
-        synchronized (mMediaPlayerInfoList) {
-            for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
-                if (entry.getValue().getPackageName().equals(packageName)) {
-                    int newAddrID = entry.getKey();
-                    if (DEBUG) {
-                        Log.v(TAG, "Set addressed #" + newAddrID + " " + entry.getValue());
+
+        synchronized (this) {
+            synchronized (mMediaPlayerInfoList) {
+                for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+                    if (entry.getValue().getPackageName().equals(packageName)) {
+                        int newAddrID = entry.getKey();
+                        if (DEBUG) {
+                            Log.v(TAG, "Set addressed #" + newAddrID + " " + entry.getValue());
+                        }
+                        updateCurrentController(newAddrID, mCurrBrowsePlayerID);
+                        updateCurrentMediaState();
+                        return;
                     }
-                    updateCurrentController(newAddrID, mCurrBrowsePlayerID);
-                    updateCurrentMediaState();
-                    return;
                 }
             }
         }
@@ -1862,15 +2064,36 @@
     private void setActiveMediaSession(MediaSession.Token token) {
         android.media.session.MediaController activeController =
                 new android.media.session.MediaController(mContext, token);
-        if (activeController.getPackageName().equals("com.android.server.telecom")) {
+        if (activeController.getPackageName().contains("telecom")) {
             Log.d(TAG, "Ignore active media session change to telecom");
             return;
         }
+        HeadsetService mService = HeadsetService.getHeadsetService();
+        if (mService != null && mService.isScoOrCallActive()) {
+            Log.v(TAG,"Ignore setActiveMediaSession for telecom, call in progress");
+            return;
+        }
+
+
+        if (mHandler.hasMessages(MESSAGE_SET_MEDIA_SESSION))
+            mHandler.removeMessages(MESSAGE_SET_MEDIA_SESSION);
         if (DEBUG) {
             Log.v(TAG, "Set active media session " + activeController.getPackageName());
         }
-        addMediaPlayerController(activeController);
-        setAddressedMediaSessionPackage(activeController.getPackageName());
+        synchronized (Avrcp.this) {
+            addMediaPlayerController(activeController);
+            setAddressedMediaSessionPackage(activeController.getPackageName());
+        }
+    }
+
+    private void setActiveMediaSession(android.media.session.MediaController mController) {
+        HeadsetService mService = HeadsetService.getHeadsetService();
+        if ((mService != null && mService.isScoOrCallActive())) {
+            Log.w(TAG, "Ignore media session during call");
+            return;
+        }
+        addMediaPlayerController(mController);
+        setAddressedMediaSessionPackage(mController.getPackageName());
     }
 
     private boolean startBrowseService(byte[] bdaddr, String packageName) {
@@ -1945,32 +2168,34 @@
 
     /* Initializes list of media players identified from session manager active sessions */
     private void initMediaPlayersList() {
-        synchronized (mMediaPlayerInfoList) {
-            // Clearing old browsable player's list
-            mMediaPlayerInfoList.clear();
+        synchronized (this) {
+            synchronized (mMediaPlayerInfoList) {
+                // Clearing old browsable player's list
+                mMediaPlayerInfoList.clear();
 
-            if (mMediaSessionManager == null) {
-                if (DEBUG) {
-                    Log.w(TAG, "initMediaPlayersList: no media session manager!");
+                if (mMediaSessionManager == null) {
+                    if (DEBUG) {
+                        Log.w(TAG, "initMediaPlayersList: no media session manager!");
+                    }
+                    return;
                 }
-                return;
-            }
 
-            List<android.media.session.MediaController> controllers =
-                    mMediaSessionManager.getActiveSessions(null);
-            if (DEBUG) {
-                Log.v(TAG, "initMediaPlayerInfoList: " + controllers.size() + " controllers");
-            }
-            /* Initializing all media players */
-            for (android.media.session.MediaController controller : controllers) {
-                addMediaPlayerController(controller);
-            }
+                List<android.media.session.MediaController> controllers =
+                        mMediaSessionManager.getActiveSessions(null);
+                if (DEBUG) {
+                    Log.v(TAG, "initMediaPlayerInfoList: " + controllers.size() + " controllers");
+                }
+                /* Initializing all media players */
+                for (android.media.session.MediaController controller : controllers) {
+                    addMediaPlayerController(controller);
+                }
 
-            updateCurrentMediaState();
+                updateCurrentMediaState();
 
-            if (mMediaPlayerInfoList.size() > 0) {
-                // Set the first one as the Addressed Player
-                updateCurrentController(mMediaPlayerInfoList.firstKey(), -1);
+                if (mMediaPlayerInfoList.size() > 0) {
+                    // Set the first one as the Addressed Player
+                    updateCurrentController(mMediaPlayerInfoList.firstKey(), -1);
+                }
             }
         }
     }
@@ -1978,11 +2203,13 @@
     private List<android.media.session.MediaController> getMediaControllers() {
         List<android.media.session.MediaController> controllers =
                 new ArrayList<android.media.session.MediaController>();
-        synchronized (mMediaPlayerInfoList) {
-            for (MediaPlayerInfo info : mMediaPlayerInfoList.values()) {
-                MediaController controller = info.getMediaController();
-                if (controller != null) {
-                    controllers.add(controller.getWrappedInstance());
+        synchronized (this) {
+           synchronized (mMediaPlayerInfoList) {
+                for (MediaPlayerInfo info : mMediaPlayerInfoList.values()) {
+                    MediaController controller = info.getMediaController();
+                    if (controller != null) {
+                        controllers.add(controller.getWrappedInstance());
+                    }
                 }
             }
         }
@@ -2019,30 +2246,32 @@
             Log.d(TAG, "Skip adding telecom to the media player info list");
             return updated;
         }
-        synchronized (mMediaPlayerInfoList) {
-            for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
-                MediaPlayerInfo current = entry.getValue();
-                int id = entry.getKey();
-                if (info.getPackageName().equals(current.getPackageName())) {
-                    if (!current.equalView(info)) {
-                        // If we would present a different player, make it a new player
-                        // so that controllers know whether a player is browsable or not.
-                        mMediaPlayerInfoList.remove(id);
-                        currentRemoved = (mCurrAddrPlayerID == id);
+        synchronized (this) {
+            synchronized (mMediaPlayerInfoList) {
+                for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+                    MediaPlayerInfo current = entry.getValue();
+                    int id = entry.getKey();
+                    if (info.getPackageName().equals(current.getPackageName())) {
+                        if (!current.equalView(info)) {
+                            // If we would present a different player, make it a new player
+                            // so that controllers know whether a player is browsable or not.
+                            mMediaPlayerInfoList.remove(id);
+                            currentRemoved = (mCurrAddrPlayerID == id);
+                            break;
+                        }
+                        updateId = id;
+                        updated = true;
                         break;
                     }
-                    updateId = id;
-                    updated = true;
-                    break;
                 }
+                if (updateId == -1) {
+                    // New player
+                    mLastUsedPlayerID++;
+                    updateId = mLastUsedPlayerID;
+                    mAvailablePlayerViewChanged = true;
+                }
+                mMediaPlayerInfoList.put(updateId, info);
             }
-            if (updateId == -1) {
-                // New player
-                mLastUsedPlayerID++;
-                updateId = mLastUsedPlayerID;
-                mAvailablePlayerViewChanged = true;
-            }
-            mMediaPlayerInfoList.put(updateId, info);
         }
         if (DEBUG) {
             Log.d(TAG, (updated ? "update #" : "add #") + updateId + ":" + info.toString());
@@ -2055,23 +2284,26 @@
 
     /** Remove all players related to |packageName| from the media player info list */
     private MediaPlayerInfo removeMediaPlayerInfo(String packageName) {
-        synchronized (mMediaPlayerInfoList) {
-            int removeKey = -1;
-            for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
-                if (entry.getValue().getPackageName().equals(packageName)) {
-                    removeKey = entry.getKey();
-                    break;
+        synchronized (this) {
+            synchronized (mMediaPlayerInfoList) {
+                int removeKey = -1;
+                for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+                    if (entry.getValue().getPackageName().equals(packageName)) {
+                        removeKey = entry.getKey();
+                        break;
+                    }
                 }
-            }
-            if (removeKey != -1) {
-                if (DEBUG) {
-                    Log.d(TAG, "remove #" + removeKey + ":" + mMediaPlayerInfoList.get(removeKey));
+                if (removeKey != -1) {
+                    if (DEBUG) {
+                        Log.d(TAG, "remove #" + removeKey + ":"
+                                + mMediaPlayerInfoList.get(removeKey));
+                    }
+                    mAvailablePlayerViewChanged = true;
+                    return mMediaPlayerInfoList.remove(removeKey);
                 }
-                mAvailablePlayerViewChanged = true;
-                return mMediaPlayerInfoList.remove(removeKey);
-            }
 
-            return null;
+                return null;
+            }
         }
     }
 
@@ -2080,14 +2312,16 @@
         if (controller == null) {
             return;
         }
-        synchronized (mMediaPlayerInfoList) {
-            for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
-                MediaPlayerInfo info = entry.getValue();
-                MediaController c = info.getMediaController();
-                if (c != null && c.equals(controller)) {
-                    info.setMediaController(null);
-                    if (entry.getKey() == mCurrAddrPlayerID) {
-                        updateCurrentController(mCurrAddrPlayerID, mCurrBrowsePlayerID);
+        synchronized (this) {
+            synchronized (mMediaPlayerInfoList) {
+                for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+                    MediaPlayerInfo info = entry.getValue();
+                    MediaController c = info.getMediaController();
+                    if (c != null && c.equals(controller)) {
+                        info.setMediaController(null);
+                        if (entry.getKey() == mCurrAddrPlayerID) {
+                            updateCurrentController(mCurrAddrPlayerID, mCurrBrowsePlayerID);
+                        }
                     }
                 }
             }
@@ -2108,13 +2342,13 @@
             case PlaybackState.STATE_PLAYING:
                 return PLAYSTATUS_PLAYING;
 
-            case PlaybackState.STATE_BUFFERING:
             case PlaybackState.STATE_STOPPED:
             case PlaybackState.STATE_NONE:
             case PlaybackState.STATE_CONNECTING:
                 return PLAYSTATUS_STOPPED;
 
             case PlaybackState.STATE_PAUSED:
+            case PlaybackState.STATE_BUFFERING:
                 return PLAYSTATUS_PAUSED;
 
             case PlaybackState.STATE_FAST_FORWARDING:
@@ -2156,6 +2390,8 @@
             featureBitsList.add(AvrcpConstants.AVRC_PF_UID_UNIQUE_BIT_NO);
             featureBitsList.add(AvrcpConstants.AVRC_PF_NOW_PLAY_BIT_NO);
             featureBitsList.add(AvrcpConstants.AVRC_PF_GET_NUM_OF_ITEMS_BIT_NO);
+            if (mAvrcpBipRsp != null)
+                featureBitsList.add(AvrcpConstants.AVRC_PF_COVER_ART_BIT_NO);
         }
 
         // converting arraylist to array for response
@@ -2195,8 +2431,10 @@
 
     private String getPackageName(int id) {
         MediaPlayerInfo player = null;
-        synchronized (mMediaPlayerInfoList) {
-            player = mMediaPlayerInfoList.getOrDefault(id, null);
+        synchronized (this) {
+            synchronized (mMediaPlayerInfoList) {
+                player = mMediaPlayerInfoList.getOrDefault(id, null);
+            }
         }
 
         if (player == null) {
@@ -2228,8 +2466,10 @@
 
     /* Returns the MediaPlayerInfo for the currently addressed media player */
     private MediaPlayerInfo getAddressedPlayerInfo() {
-        synchronized (mMediaPlayerInfoList) {
-            return mMediaPlayerInfoList.getOrDefault(mCurrAddrPlayerID, null);
+        synchronized (this) {
+            synchronized (mMediaPlayerInfoList) {
+                return mMediaPlayerInfoList.getOrDefault(mCurrAddrPlayerID, null);
+            }
         }
     }
 
@@ -2238,114 +2478,131 @@
      * null if package name not found in media players list
      */
     private MediaPlayerInfo getMediaPlayerInfo(String packageName) {
-        synchronized (mMediaPlayerInfoList) {
-            if (mMediaPlayerInfoList.isEmpty()) {
+        synchronized (this) {
+            synchronized (mMediaPlayerInfoList) {
+                if (mMediaPlayerInfoList.isEmpty()) {
+                    if (DEBUG) {
+                        Log.v(TAG, "getMediaPlayerInfo: Media players list empty");
+                    }
+                    return null;
+                }
+
+                for (MediaPlayerInfo info : mMediaPlayerInfoList.values()) {
+                    if (packageName.equals(info.getPackageName())) {
+                        if (DEBUG) {
+                            Log.v(TAG, "getMediaPlayerInfo: Found " + packageName);
+                        }
+                        return info;
+                    }
+                }
                 if (DEBUG) {
-                    Log.v(TAG, "getMediaPlayerInfo: Media players list empty");
+                    Log.w(TAG, "getMediaPlayerInfo: " + packageName + " not found");
                 }
                 return null;
             }
-
-            for (MediaPlayerInfo info : mMediaPlayerInfoList.values()) {
-                if (packageName.equals(info.getPackageName())) {
-                    if (DEBUG) {
-                        Log.v(TAG, "getMediaPlayerInfo: Found " + packageName);
-                    }
-                    return info;
-                }
-            }
-            if (DEBUG) {
-                Log.w(TAG, "getMediaPlayerInfo: " + packageName + " not found");
-            }
-            return null;
         }
     }
 
     /* prepare media list & return the media player list response object */
     private MediaPlayerListRsp prepareMediaPlayerRspObj() {
-        synchronized (mMediaPlayerInfoList) {
-            // TODO(apanicke): This hack will go away as soon as a developer
-            // option to enable or disable player selection is created. Right
-            // now this is needed to fix BMW i3 carkits and any other carkits
-            // that might try to connect to a player that isnt the current
-            // player based on this list
-            int numPlayers = 1;
+        synchronized (this) {
+            synchronized (mMediaPlayerInfoList) {
+                // TODO(apanicke): This hack will go away as soon as a developer
+                // option to enable or disable player selection is created. Right
+                // now this is needed to fix BMW i3 carkits and any other carkits
+                // that might try to connect to a player that isnt the current
+                // player based on this list
+                int numPlayers = 1;
 
-            int[] playerIds = new int[numPlayers];
-            byte[] playerTypes = new byte[numPlayers];
-            int[] playerSubTypes = new int[numPlayers];
-            String[] displayableNameArray = new String[numPlayers];
-            byte[] playStatusValues = new byte[numPlayers];
-            short[] featureBitMaskValues =
-                    new short[numPlayers * AvrcpConstants.AVRC_FEATURE_MASK_SIZE];
+                int[] playerIds = new int[numPlayers];
+                byte[] playerTypes = new byte[numPlayers];
+                int[] playerSubTypes = new int[numPlayers];
+                String[] displayableNameArray = new String[numPlayers];
+                byte[] playStatusValues = new byte[numPlayers];
+                short[] featureBitMaskValues =
+                        new short[numPlayers * AvrcpConstants.AVRC_FEATURE_MASK_SIZE];
 
-            // Reserve the first spot for the currently addressed player if
-            // we have one
-            int players = mMediaPlayerInfoList.containsKey(mCurrAddrPlayerID) ? 1 : 0;
-            for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
-                int idx = players;
-                if (entry.getKey() == mCurrAddrPlayerID) {
-                    idx = 0;
-                } else {
-                    continue; // TODO(apanicke): Remove, see above note
-                }
-                MediaPlayerInfo info = entry.getValue();
-                playerIds[idx] = entry.getKey();
-                playerTypes[idx] = info.getMajorType();
-                playerSubTypes[idx] = info.getSubType();
-                displayableNameArray[idx] = info.getDisplayableName();
-                playStatusValues[idx] = info.getPlayStatus();
+                // Reserve the first spot for the currently addressed player if
+                // we have one
+                int players = mMediaPlayerInfoList.containsKey(mCurrAddrPlayerID) ? 1 : 0;
+                for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+                    int idx = players;
+                    if (entry.getKey() == mCurrAddrPlayerID) {
+                        idx = 0;
+                    } else {
+                        continue; // TODO(apanicke): Remove, see above note
+                    }
+                    MediaPlayerInfo info = entry.getValue();
+                    playerIds[idx] = entry.getKey();
+                    playerTypes[idx] = info.getMajorType();
+                    playerSubTypes[idx] = info.getSubType();
+                    displayableNameArray[idx] = info.getDisplayableName();
+                    playStatusValues[idx] = info.getPlayStatus();
 
-                short[] featureBits = info.getFeatureBitMask();
-                for (int numBit = 0; numBit < featureBits.length; numBit++) {
-                    /* gives which octet this belongs to */
-                    byte octet = (byte) (featureBits[numBit] / 8);
-                    /* gives the bit position within the octet */
-                    byte bit = (byte) (featureBits[numBit] % 8);
-                    featureBitMaskValues[(idx * AvrcpConstants.AVRC_FEATURE_MASK_SIZE) + octet] |=
-                            (1 << bit);
+                    short[] featureBits = info.getFeatureBitMask();
+                    for (int numBit = 0; numBit < featureBits.length; numBit++) {
+                        /* gives which octet this belongs to */
+                        byte octet = (byte) (featureBits[numBit] / 8);
+                        /* gives the bit position within the octet */
+                        byte bit = (byte) (featureBits[numBit] % 8);
+                        featureBitMaskValues[(idx * AvrcpConstants.AVRC_FEATURE_MASK_SIZE) + octet]
+                                |= (1 << bit);
+                    }
+
+                    /* printLogs */
+                    if (DEBUG) {
+                        Log.d(TAG, "Player " + playerIds[idx] + ": " + displayableNameArray[idx]
+                                + " type: " + playerTypes[idx] + ", " + playerSubTypes[idx]
+                                + " status: " + playStatusValues[idx]);
+                    }
+
+                    if (idx != 0) {
+                        players++;
+                    }
                 }
 
-                /* printLogs */
                 if (DEBUG) {
-                    Log.d(TAG, "Player " + playerIds[idx] + ": " + displayableNameArray[idx]
-                            + " type: " + playerTypes[idx] + ", " + playerSubTypes[idx]
-                            + " status: " + playStatusValues[idx]);
+                    Log.d(TAG, "prepareMediaPlayerRspObj: numPlayers = " + numPlayers);
                 }
 
-                if (idx != 0) {
-                    players++;
-                }
+                return new MediaPlayerListRsp(AvrcpConstants.RSP_NO_ERROR, sUIDCounter, numPlayers,
+                        AvrcpConstants.BTRC_ITEM_PLAYER, playerIds, playerTypes, playerSubTypes,
+                        playStatusValues, featureBitMaskValues, displayableNameArray);
             }
-
-            if (DEBUG) {
-                Log.d(TAG, "prepareMediaPlayerRspObj: numPlayers = " + numPlayers);
-            }
-
-            return new MediaPlayerListRsp(AvrcpConstants.RSP_NO_ERROR, sUIDCounter, numPlayers,
-                    AvrcpConstants.BTRC_ITEM_PLAYER, playerIds, playerTypes, playerSubTypes,
-                    playStatusValues, featureBitMaskValues, displayableNameArray);
         }
     }
 
     /* build media player list and send it to remote. */
     private void handleMediaPlayerListRsp(AvrcpCmd.FolderItemsCmd folderObj) {
         MediaPlayerListRsp rspObj = null;
-        synchronized (mMediaPlayerInfoList) {
-            int numPlayers = mMediaPlayerInfoList.size();
-            if (numPlayers == 0) {
-                mediaPlayerListRspNative(folderObj.mAddress, AvrcpConstants.RSP_NO_AVBL_PLAY,
-                        (short) 0, (byte) 0, 0, null, null, null, null, null, null);
-                return;
+        synchronized (this) {
+            synchronized (mMediaPlayerInfoList) {
+                int numPlayers = mMediaPlayerInfoList.size();
+                if (numPlayers == 0) {
+                    mediaPlayerListRspNative(folderObj.mAddress, AvrcpConstants.RSP_NO_AVBL_PLAY,
+                            (short) 0, (byte) 0, 0, null, null, null, null, null, null);
+                    return;
+                }
+                if (folderObj.mStartItem >= numPlayers || folderObj.mStartItem >= 1) {
+                    Log.i(TAG, "handleMediaPlayerListRsp: start = " + folderObj.mStartItem
+                            + " > num of items = " + numPlayers);
+                    mediaPlayerListRspNative(folderObj.mAddress, AvrcpConstants.RSP_INV_RANGE,
+                            (short) 0, (byte) 0, 0, null, null, null, null, null, null);
+                    return;
+                }
+                if (mCurrAddrPlayerID == NO_PLAYER_ID) {
+                    short[] featureBitsArray = {0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x01, 0x04,
+                                                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+                    Log.i(TAG, "handleMediaPlayerListRsp: Send dummy player response");
+                    mediaPlayerListRspNative(folderObj.mAddress, (int)AvrcpConstants.RSP_NO_ERROR,
+                            (int)sUIDCounter, AvrcpConstants.BTRC_ITEM_PLAYER, 1, new int[] {0},
+                            new byte[] {AvrcpConstants.PLAYER_TYPE_AUDIO}, new int[] {1},
+                            new byte[] {PLAYSTATUS_STOPPED}, featureBitsArray,
+                            new String[] {"Dummy Player"});
+                    return;
+                }
+                rspObj = prepareMediaPlayerRspObj();
             }
-            if (folderObj.mStartItem >= numPlayers) {
-                Log.i(TAG, "handleMediaPlayerListRsp: start = " + folderObj.mStartItem
-                        + " > num of items = " + numPlayers);
-                mediaPlayerListRspNative(folderObj.mAddress, AvrcpConstants.RSP_INV_RANGE,
-                        (short) 0, (byte) 0, 0, null, null, null, null, null, null);
-                return;
-            }
-            rspObj = prepareMediaPlayerRspObj();
         }
         if (DEBUG) {
             Log.d(TAG, "handleMediaPlayerListRsp: sending " + rspObj.mNumItems + " players");
@@ -2359,6 +2616,8 @@
     /* unregister to the old controller, update new IDs and register to the new controller */
     private boolean updateCurrentController(int addrId, int browseId) {
         boolean registerRsp = true;
+        int preAddrId = mCurrAddrPlayerID;
+        int preBrowseId = mCurrBrowsePlayerID;
 
         updateNewIds(addrId, browseId);
 
@@ -2381,6 +2640,7 @@
                     mMediaController.registerCallback(mMediaControllerCb, mHandler);
                 } else {
                     registerRsp = false;
+                    updateNewIds(preAddrId, preBrowseId);
                 }
             }
         }
@@ -2441,6 +2701,13 @@
     }
 
     private void handlePlayItemResponse(byte[] bdaddr, byte[] uid, byte scope) {
+        HeadsetService mService = HeadsetService.getHeadsetService();
+        if ((mService != null) && mService.isScoOrCallActive()) {
+            Log.w(TAG, "Remote requesting play item while call is active");
+            playItemRspNative(bdaddr, AvrcpConstants.RSP_MEDIA_IN_USE);
+            return;
+        }
+
         if (scope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
             mAddressedMediaPlayer.playItem(bdaddr, uid, mMediaController);
         } else {
@@ -2462,10 +2729,9 @@
 
     private void handleGetItemAttr(AvrcpCmd.ItemAttrCmd itemAttr) {
         if (itemAttr.mUidCounter != sUIDCounter) {
-            Log.e(TAG, "handleGetItemAttr: invaild uid counter.");
-            getItemAttrRspNative(itemAttr.mAddress, AvrcpConstants.RSP_UID_CHANGED, (byte) 0, null,
-                    null);
-            return;
+            itemAttr.mUidCounter = sUIDCounter;
+            Log.e(TAG, "handleGetItemAttr: invalid uid counter, assign new value = "
+                    + itemAttr.mUidCounter);
         }
         if (itemAttr.mScope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
             if (mCurrAddrPlayerID == NO_PLAYER_ID) {
@@ -2490,8 +2756,10 @@
         // for scope as media player list
         if (scope == AvrcpConstants.BTRC_SCOPE_PLAYER_LIST) {
             int numPlayers = 0;
-            synchronized (mMediaPlayerInfoList) {
-                numPlayers = mMediaPlayerInfoList.size();
+            synchronized(this) {
+                synchronized (mMediaPlayerInfoList) {
+                    numPlayers = mMediaPlayerInfoList.containsKey(mCurrAddrPlayerID) ? 1 : 0;
+                }
             }
             if (DEBUG) {
                 Log.d(TAG, "handleGetTotalNumOfItemsResponse: " + numPlayers + " players.");
@@ -2580,12 +2848,14 @@
         }
         ProfileService.println(sb, "");
         ProfileService.println(sb, "Media Players:");
-        synchronized (mMediaPlayerInfoList) {
-            for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
-                int key = entry.getKey();
-                ProfileService.println(sb,
-                        ((mCurrAddrPlayerID == key) ? " *#" : "  #") + entry.getKey() + ": " + entry
-                                .getValue());
+        synchronized(this) {
+            synchronized (mMediaPlayerInfoList) {
+                for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+                    int key = entry.getKey();
+                    ProfileService.println(sb,
+                            ((mCurrAddrPlayerID == key) ? " *#" : "  #") + entry.getKey() + ": "
+                                    + entry.getValue());
+                }
             }
         }
 
@@ -2850,9 +3120,60 @@
         if (state == AvrcpConstants.KEY_STATE_RELEASE) {
             action = KeyEvent.ACTION_UP;
         }
+
+        if (mLastPassthroughcmd == KeyEvent.KEYCODE_UNKNOWN) {
+            if (isPlayingState(mCurrentPlayState) && mAudioManager.isMusicActive() &&
+                    (mA2dpState == BluetoothA2dp.STATE_PLAYING) &&
+                    (code == KeyEvent.KEYCODE_MEDIA_PLAY)) {
+                Log.w(TAG, "Ignoring passthrough command play" + op + " state " + state +
+                        "in music playing");
+                return;
+            }
+            if (!isPlayingState(mCurrentPlayState) && (!mAudioManager.isMusicActive())
+                    && (mA2dpState == BluetoothA2dp.STATE_NOT_PLAYING) &&
+                    (code == KeyEvent.KEYCODE_MEDIA_PAUSE)) {
+                Log.w(TAG, "Ignoring passthrough command pause" + op + " state " + state +
+                        "in music playing");
+                return;
+            }
+        }
+
         KeyEvent event = new KeyEvent(action, code);
         if (!KeyEvent.isMediaKey(code)) {
             Log.w(TAG, "Passthrough non-media key " + op + " (code " + code + ") state " + state);
+        } else {
+            if (code == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
+                if (action == KeyEvent.ACTION_DOWN) {
+                    mFastforward = true;
+                } else {
+                    mFastforward = false;
+                }
+            } else if (code == KeyEvent.KEYCODE_MEDIA_REWIND) {
+                if (action == KeyEvent.ACTION_DOWN) {
+                    mRewind = true;
+                } else {
+                    mRewind = false;
+                }
+            } else {
+                mRewind = false;
+                mFastforward = false;
+            }
+        }
+        /* IOT Fix as some remote recognise FF/Rewind state as non-playing hence send
+         * changed response at the time of Release of Fast-Forward/Rewind Button */
+        if ((code == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD || code == KeyEvent.KEYCODE_MEDIA_REWIND)
+                && (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM)
+                && (action == KeyEvent.ACTION_UP)) {
+            sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_CHANGED, mReportedPlayStatus);
+            Log.d(TAG, "Sending playback status CHANGED rsp on FF/Rewind key release");
+            mLastPassthroughcmd = KeyEvent.KEYCODE_UNKNOWN;
+        }
+
+        Log.d(TAG, "cached passthrough: " + mLastPassthroughcmd + "current passthrough: " + code);
+        if ((mLastPassthroughcmd != KeyEvent.KEYCODE_UNKNOWN) && (mLastPassthroughcmd != code)) {
+            mLastPassthroughcmd = KeyEvent.KEYCODE_UNKNOWN;
+        } else {
+            mLastPassthroughcmd = code;
         }
 
         mMediaSessionManager.dispatchMediaKeyEvent(event);
diff --git a/src/com/android/bluetooth/avrcp/AvrcpConstants.java b/src/com/android/bluetooth/avrcp/AvrcpConstants.java
index 50a2eeb..94419ae 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpConstants.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpConstants.java
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.avrcp;
 
+import android.util.Log;
+
 /*************************************************************************************************
  * Grouped all HAL constants into a file to be consistent with the stack.
  * Moved the constants used in Avrcp to this new file to be used across multiple files.
@@ -145,7 +147,7 @@
     static final short AVRC_PF_UID_UNIQUE_BIT_NO = 62;
     static final short AVRC_PF_NOW_PLAY_BIT_NO = 65;
     static final short AVRC_PF_GET_NUM_OF_ITEMS_BIT_NO = 67;
-
+    static final short AVRC_PF_COVER_ART_BIT_NO = 68;
     static final byte PLAYER_TYPE_AUDIO = 1;
     static final int PLAYER_SUBTYPE_NONE = 0;
 
@@ -162,4 +164,16 @@
 
     static final int KEY_STATE_PRESS = 1;
     static final int KEY_STATE_RELEASE = 0;
+
+    static final int GET_ATTRIBUTE_IDS = 0;
+    static final int GET_VALUE_IDS = 1;
+    static final int GET_ATTRIBUTE_TEXT = 2;
+    static final int GET_VALUE_TEXT     = 3;
+    static final int GET_ATTRIBUTE_VALUES = 4;
+    static final int NOTIFY_ATTRIBUTE_VALUES = 5;
+    static final int SET_ATTRIBUTE_VALUES  = 6;
+    static final int GET_INVALID = 0xff;
+
+    public static final String TAG = "Avrcp";
+    public static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 }
diff --git a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
index 0889d90..0d8c45e 100644
--- a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
+++ b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
@@ -40,7 +40,7 @@
  ************************************************************************************************/
 
 class BrowsedMediaPlayer {
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
     private static final String TAG = "BrowsedMediaPlayer";
 
     /* connection state with MediaBrowseService */
@@ -48,8 +48,10 @@
     private static final int CONNECTED = 1;
     private static final int SUSPENDED = 2;
 
+    private static final int BROWSED_ITEM_ID_INDEX = 2;
+    private static final int BROWSED_FOLDER_ID_INDEX = 4;
     private static final String[] ROOT_FOLDER = {"root"};
-
+    private static boolean mPlayerRoot = false;
     /*  package and service name of target Media Player which is set for browsing */
     private String mPackageName;
     private String mConnectingPackageName;
@@ -58,6 +60,9 @@
     private AvrcpMediaRspInterface mMediaInterface;
     private byte[] mBDAddr;
 
+    private String mCurrentBrowsePackage;
+    private String mCurrentBrowseClass;
+
     /* Object used to connect to MediaBrowseService of Media Player */
     private MediaBrowser mMediaBrowser = null;
     private MediaController mMediaController = null;
@@ -69,12 +74,15 @@
 
     /* stores the path trail during changePath */
     private Stack<String> mPathStack = null;
-
+    private Stack<String> mLocalPathCache = null;
     /* Number of items in current folder */
     private int mCurrFolderNumItems = 0;
 
-    /* store mapping between uid(Avrcp) and mediaId(Media Player). */
-    private HashMap<Integer, String> mHmap = new HashMap<Integer, String>();
+    /* store mapping between uid(Avrcp) and mediaId(Media Player) for Media Item */
+    private HashMap<Integer, String> mMediaHmap = new HashMap<Integer, String>();
+
+    /* store mapping between uid(Avrcp) and mediaId(Media Player) for Folder Item */
+    private HashMap<Integer, String> mFolderHmap = new HashMap<Integer, String>();
 
     /* command objects from avrcp handler */
     private AvrcpCmd.FolderItemsCmd mFolderItemsReqObj;
@@ -146,6 +154,7 @@
                         if (DEBUG) {
                             Log.d(TAG, "sending setbrowsed player rsp");
                         }
+                        Log.w(TAG, "sending setbrowsed player rsp");
                         mFolderItems = children;
                         mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR,
                                 (byte) 0x00, children.size(), ROOT_FOLDER);
@@ -155,6 +164,7 @@
                         mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR,
                                 mCurrFolderNumItems);
                     }
+                    refreshFolderItems(mFolderItems);
                     mMediaBrowser.unsubscribe(parentId);
                 }
 
@@ -280,8 +290,10 @@
     /* initialize mediacontroller in order to communicate with media player. */
     private void onBrowseConnect(String connectedPackage, MediaBrowser browser) {
         if (!connectedPackage.equals(mConnectingPackageName)) {
-            Log.w(TAG, "onBrowseConnect: recieved callback for package we aren't connecting to "
-                    + connectedPackage);
+            Log.w(TAG, "onBrowseConnect: recieved callback for package" + mConnectingPackageName +
+                    "we aren't connecting to " + connectedPackage);
+            mMediaInterface.setBrowsedPlayerRsp(
+                    mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0x00, 0, null);
             return;
         }
         mConnectingPackageName = null;
@@ -310,17 +322,40 @@
                 /* get rootfolder uid from media player */
                 if (mMediaId == null) {
                     mMediaId = mMediaBrowser.getRoot();
+                    Log.d(TAG, "media browser root = " + mMediaId);
+
+                    if (mMediaId == null || mMediaId.length() == 0) {
+                        Log.e(TAG, "onBrowseConnect: root value is empty or null");
+                        mMediaInterface.setBrowsedPlayerRsp(
+                                mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0x00, 0, null);
+                        return;
+                    }
+
                     /*
                      * assuming that root folder uid will not change on uids changed
                      */
                     mRootFolderUid = mMediaId;
                     /* store root folder uid to stack */
                     mPathStack.push(mMediaId);
+                    String [] ExternalPath = mMediaId.split("/");
+                    if (ExternalPath != null) {
+                        Log.d(TAG,"external path length: " + ExternalPath.length);
+                        if (ExternalPath.length == 1) {
+                            mLocalPathCache.push(mMediaId);
+                        } else if (ExternalPath.length == 0 && mMediaId.equals("/")) {
+                            mPlayerRoot = true;
+                            mLocalPathCache.push(mMediaId);
+                        } else {
+                            //to trim the root in GMP which comes as "com.google.android.music.generic/root"
+                            mLocalPathCache.push(ExternalPath[ExternalPath.length - 1]);
+                        }
+                    }
+                    /* get root folder items */
+                    Log.e(TAG, "onBrowseConnect: subscribe event for FolderCb");
+                    mMediaBrowser.subscribe(mRootFolderUid, mFolderItemsCb);
                 }
 
                 mMediaController = MediaControllerFactory.make(mContext, token);
-                /* get root folder items */
-                mMediaBrowser.subscribe(mRootFolderUid, mFolderItemsCb);
                 return;
             }
         } catch (NullPointerException ex) {
@@ -333,26 +368,120 @@
     }
 
     public void setBrowsed(String packageName, String cls) {
+        Log.w(TAG, "!! In setBrowse function !!" + mFolderItems);
+        if ((mPackageName != null && packageName != null
+                && !mPackageName.equals(packageName)) || (mFolderItems == null)) {
+            Log.d(TAG, "setBrowse for packageName = " + packageName);
+            mConnectingPackageName = packageName;
+            mPackageName = packageName;
+            mClassName = cls;
+
+           /* cleanup variables from previous browsed calls */
+           mFolderItems = null;
+           mMediaId = null;
+           mRootFolderUid = null;
+           mPlayerRoot = false;
+           /*
+            * create stack to store the navigation trail (current folder ID). This
+            * will be required while navigating up the folder
+            */
+           mPathStack = new Stack<String>();
+           mLocalPathCache = new Stack<String>();
+           /* Bind to MediaBrowseService of MediaPlayer */
+           MediaConnectionCallback callback = new MediaConnectionCallback(packageName);
+           MediaBrowser tempBrowser = new MediaBrowser(
+                   mContext, new ComponentName(packageName, mClassName), callback, null);
+           callback.setBrowser(tempBrowser);
+           tempBrowser.connect();
+        } else if (mFolderItems != null) {
+            mPackageName = packageName;
+            mClassName = cls;
+            int rsp_status = AvrcpConstants.RSP_NO_ERROR;
+            int folder_depth = (mPathStack.size() > 0) ? (mPathStack.size() - 1) : 0;
+            if (!mPathStack.empty()) {
+                Log.d(TAG, "~~current Path = " + mPathStack.peek());
+                if (mPathStack.size() > 1) {
+                    String top = mPathStack.peek();
+                    mPathStack.pop();
+                    String path = mPathStack.peek();
+                    mPathStack.push(top);
+                    String [] ExternalPath = path.split("/");
+                    if (!mPlayerRoot && ExternalPath != null && ExternalPath.length > 1) {
+                        Log.d(TAG,"external path length: " + ExternalPath.length);
+                        String [] folderPath = new String[ExternalPath.length - 1];
+                        for (int i = 0; i < (ExternalPath.length - 1); i++) {
+                             folderPath[i] = ExternalPath[i + 1];
+                             Log.d(TAG,"folderPath[" + i + "] = " + folderPath[i]);
+                        }
+                        mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status,
+                            (byte)folder_depth, mFolderItems.size(), folderPath);
+                    } else if (!mPlayerRoot && mLocalPathCache.size() > 1 && ExternalPath.length == 1) {
+                        String [] folderPath = new String[mLocalPathCache.size() - 1];
+                        folderPath = mLocalPathCache.toArray(folderPath);
+                        for (int i = 0; i < mLocalPathCache.size() - 1; i++) {
+                             Log.d(TAG,"folderPath[" + i + "] = " + folderPath[i]);
+                        }
+                        mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status,
+                            (byte)folder_depth, mFolderItems.size(), folderPath);
+                    } else if (mPlayerRoot && mLocalPathCache.size() > 1) {
+                        String [] folderPath = new String[mLocalPathCache.size() - 1];
+                        folderPath = mLocalPathCache.toArray(folderPath);
+                        for (int i = 0; i < mLocalPathCache.size() - 1; i++) {
+                             Log.d(TAG,"folderPath[" + i + "] = " + folderPath[i]);
+                        }
+                        mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status,
+                            (byte)folder_depth, mFolderItems.size(), folderPath);
+                    } else {
+                        Log.e(TAG, "sending internal error !!!");
+                        rsp_status = AvrcpConstants.RSP_INTERNAL_ERR;
+                        mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status, (byte)0x00, 0, null);
+                    }
+                } else if (mPathStack.size() == 1) {
+                    Log.d(TAG, "On root send SetBrowse response with root properties");
+                    mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status, (byte)folder_depth,
+                            mFolderItems.size(), ROOT_FOLDER);
+                }
+            } else {
+                Log.e(TAG, "Path Stack empty sending internal error !!!");
+                rsp_status = AvrcpConstants.RSP_INTERNAL_ERR;
+                mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status, (byte)0x00, 0, null);
+            }
+            Log.d(TAG, "send setbrowse rsp status=" + rsp_status + " folder_depth=" + folder_depth);
+        }
+    }
+
+    public void TryReconnectBrowse(String packageName, String cls) {
+        Log.w(TAG, "Try reconnection with Browser service for package = " + packageName);
         mConnectingPackageName = packageName;
+        mPackageName = packageName;
         mClassName = cls;
+
         /* cleanup variables from previous browsed calls */
         mFolderItems = null;
         mMediaId = null;
         mRootFolderUid = null;
-        /*
-         * create stack to store the navigation trail (current folder ID). This
-         * will be required while navigating up the folder
-         */
+        mPlayerRoot = false;
+
+        if (mPathStack != null)
+            mPathStack = null;
         mPathStack = new Stack<String>();
 
-        /* Bind to MediaBrowseService of MediaPlayer */
-        MediaConnectionCallback callback = new MediaConnectionCallback(packageName);
-        MediaBrowser tempBrowser =
-                new MediaBrowser(mContext, new ComponentName(packageName, mClassName), callback,
-                        null);
-        callback.setBrowser(tempBrowser);
+        if (mLocalPathCache != null)
+            mLocalPathCache = null;
+        mLocalPathCache = new Stack<String>();
 
+        MediaConnectionCallback callback = new MediaConnectionCallback(packageName);
+        MediaBrowser tempBrowser = new MediaBrowser(
+                mContext, new ComponentName(packageName, cls), callback, null);
+        callback.setBrowser(tempBrowser);
         tempBrowser.connect();
+        Log.w(TAG, "Reconnected with Browser service");
+    }
+
+    public void setCurrentPackage(String packageName, String cls) {
+        Log.w(TAG, "Set current Browse based on Addr Player as " + packageName);
+        mCurrentBrowsePackage = packageName;
+        mCurrentBrowseClass = cls;
     }
 
     /* called when connection to media player is closed */
@@ -362,13 +491,16 @@
         }
 
         if (mConnState != DISCONNECTED) {
-            mMediaBrowser.disconnect();
+            if (mMediaBrowser != null) mMediaBrowser.disconnect();
         }
 
-        mHmap = null;
+        mMediaHmap = null;
+        mFolderHmap = null;
         mMediaController = null;
         mMediaBrowser = null;
         mPathStack = null;
+        mLocalPathCache = null;
+        mPlayerRoot = false;
     }
 
     public boolean isPlayerConnected() {
@@ -403,7 +535,7 @@
 
         /* check direction and change the path */
         if (direction == AvrcpConstants.DIR_DOWN) { /* move down */
-            if ((newPath = byteToString(folderUid)) == null) {
+            if ((newPath = byteToStringFolder(folderUid)) == null) {
                 Log.e(TAG, "Could not get media item from folder Uid, sending err response");
                 mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM, 0);
             } else if (!isBrowsableFolderDn(newPath)) {
@@ -416,6 +548,26 @@
                 mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
             } else {
                 mMediaBrowser.subscribe(newPath, mFolderItemsCb);
+                String [] ExternalPath = newPath.split("/");
+                if (ExternalPath != null) {
+                    Log.d(TAG,"external path length: " + ExternalPath.length);
+                    if (ExternalPath.length == 1) {
+                        //when external path length is 1 then extract the folder name from index 4
+                        String folder_name = parseQueueId(newPath, BROWSED_FOLDER_ID_INDEX);
+                        Log.d(TAG,"folder path: " + folder_name);
+                        if (folder_name != null) {
+                            mLocalPathCache.push(folder_name);
+                        } else {
+                            mLocalPathCache.push(newPath);
+                        }
+                    } else {
+                        String folderPath = ExternalPath[ExternalPath.length - 1];
+                        if (folderPath != null) {
+                            Log.d(TAG,"folder path: " + folderPath);
+                            mLocalPathCache.push(folderPath);
+                        }
+                    }
+                }
                 /* assume that call is success and update stack with new folder path */
                 mPathStack.push(newPath);
             }
@@ -429,6 +581,7 @@
             } else {
                 /* move folder up */
                 mPathStack.pop();
+                mLocalPathCache.pop();
                 newPath = mPathStack.peek();
                 mMediaBrowser.subscribe(newPath, mFolderItemsCb);
             }
@@ -445,7 +598,7 @@
         }
 
         /* check if uid is valid by doing a lookup in hashmap */
-        mediaID = byteToString(itemAttr.mUid);
+        mediaID = byteToStringMedia(itemAttr.mUid);
         if (mediaID == null) {
             Log.e(TAG, "uid is invalid");
             mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM, null);
@@ -492,18 +645,17 @@
     }
 
     public void getFolderItemsVFS(AvrcpCmd.FolderItemsCmd reqObj) {
-        if (!isPlayerConnected()) {
-            Log.e(TAG, "unable to connect to media player, sending internal error");
-            /* unable to connect to media player. Send error response to remote device */
-            mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
-            return;
-        }
-
         if (DEBUG) {
             Log.d(TAG, "getFolderItemsVFS");
         }
         mFolderItemsReqObj = reqObj;
 
+        if ((mCurrentBrowsePackage != null) && (!mCurrentBrowsePackage.equals(mPackageName))) {
+            Log.w(TAG, "Try reconnection with Browser service as addressed pkg is changed = "
+                    + mCurrentBrowsePackage + "from " + mPackageName);
+            TryReconnectBrowse(mCurrentBrowsePackage, mCurrentBrowseClass);
+        }
+
         if (mFolderItems == null) {
             /* Failed to fetch folder items from media player. Send error to remote device */
             Log.e(TAG, "Failed to fetch folder items during getFolderItemsVFS");
@@ -523,7 +675,7 @@
 
         if (isPlayerConnected()) {
             /* check if uid is valid */
-            if ((folderUid = byteToString(uid)) == null) {
+            if ((folderUid = byteToStringMedia(uid)) == null) {
                 Log.e(TAG, "uid is invalid!");
                 mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM);
                 return;
@@ -632,7 +784,12 @@
                 folderDataNative.mPlayable[itemIndex] = AvrcpConstants.ITEM_NOT_PLAYABLE;
             }
             /* set uid for current item */
-            byte[] uid = stringToByte(item.getDescription().getMediaId());
+            byte[] uid;
+            if (folderDataNative.mItemTypes[itemIndex] == AvrcpConstants.BTRC_ITEM_MEDIA)
+                uid = stringToByteMedia(item.getDescription().getMediaId(), BROWSED_ITEM_ID_INDEX);
+            else
+                uid = stringToByteFolder(item.getDescription().getMediaId());
+
             for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) {
                 folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx];
             }
@@ -731,8 +888,8 @@
                     break;
 
                 case AvrcpConstants.ATTRID_COVER_ART:
-                    Log.e(TAG, "getAttrValue: Cover art attribute not supported");
-                    return null;
+                    attrValue = Avrcp_ext.getImgHandleFromTitle(desc.getTitle().toString());
+                    break;
 
                 default:
                     Log.e(TAG, "getAttrValue: Unknown attribute ID requested: " + attr);
@@ -748,7 +905,7 @@
             }
         }
         if (DEBUG) {
-            Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + "attr id:" + attr);
+            Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + " attr id: " + attr);
         }
         return attrValue;
     }
@@ -781,24 +938,53 @@
         return true;
     }
 
-    /* convert uid to mediaId */
-    private String byteToString(byte[] byteArray) {
+    private String parseQueueId(String mediaId, int mId) {
+        if (isNumeric(mediaId)) {
+            Log.d(TAG, "Get queue id: " + mediaId);
+            return mediaId.trim();
+        } else {
+            String[] mediaIdItems = mediaId.split(",");
+            if (mediaIdItems != null && mediaIdItems.length > mId) {
+                Log.d(TAG, "Get queue id: " + mediaIdItems[mId]);
+                return mediaIdItems[mId].trim();
+            }
+        }
+
+        Log.d(TAG, "Unkown queue id");
+        return null;
+    }
+
+    /* convert uid to mediaId for Media item*/
+    private String byteToStringMedia(byte[] byteArray) {
         int uid = new BigInteger(byteArray).intValue();
-        String mediaId = mHmap.get(uid);
+        String mediaId = mMediaHmap.get(uid);
         return mediaId;
     }
 
-    /* convert mediaId to uid */
-    private byte[] stringToByte(String mediaId) {
+    /* convert uid to mediaId for Folder item*/
+    private String byteToStringFolder(byte[] byteArray) {
+        int uid = new BigInteger(byteArray).intValue();
+        String mediaId = mFolderHmap.get(uid);
+        return mediaId;
+    }
+
+    /* convert mediaId to uid for Media item*/
+    private byte[] stringToByteMedia(String mediaId, int id) {
         /* check if this mediaId already exists in hashmap */
-        if (!mHmap.containsValue(mediaId)) { /* add to hashmap */
-            // Offset by one as uid 0 is reserved
-            int uid = mHmap.size() + 1;
-            mHmap.put(uid, mediaId);
+        if (!mMediaHmap.containsValue(mediaId)) { /* add to hashmap */
+            int uid;
+            String queueId = parseQueueId(mediaId, id);
+            if (queueId == null) {
+                uid = mMediaHmap.size() + 1;
+            } else {
+                uid = Integer.valueOf(queueId).intValue();
+            }
+
+            mMediaHmap.put(uid, mediaId);
             return intToByteArray(uid);
         } else { /* search key for give mediaId */
-            for (int uid : mHmap.keySet()) {
-                if (mHmap.get(uid).equals(mediaId)) {
+            for (int uid : mMediaHmap.keySet()) {
+                if (mMediaHmap.get(uid).equals(mediaId)) {
                     return intToByteArray(uid);
                 }
             }
@@ -806,6 +992,37 @@
         return null;
     }
 
+    /* convert mediaId to uid for Folder item*/
+    private byte[] stringToByteFolder(String mediaId) {
+        /* check if this mediaId already exists in hashmap */
+        if (!mFolderHmap.containsValue(mediaId)) { /* add to hashmap */
+            // Offset by one as uid 0 is reserved
+            int uid = mFolderHmap.size() + 1;
+            mFolderHmap.put(uid, mediaId);
+            return intToByteArray(uid);
+        } else { /* search key for give mediaId */
+            for (int uid : mFolderHmap.keySet()) {
+                if (mFolderHmap.get(uid).equals(mediaId)) {
+                    return intToByteArray(uid);
+                }
+            }
+        }
+        return null;
+    }
+
+    private void refreshFolderItems(List<MediaBrowser.MediaItem> folderItems) {
+        for (int itemIndex = 0; itemIndex < folderItems.size(); itemIndex++) {
+            MediaBrowser.MediaItem item = folderItems.get(itemIndex);
+            int flags = item.getFlags();
+            if ((flags & MediaBrowser.MediaItem.FLAG_BROWSABLE) != 0) {
+                Log.d(TAG, "Folder item, no need refresh hashmap from mediaId to uid");
+            } else {
+                Log.d(TAG, "Media item, refresh haspmap from mediaId to uid");
+                stringToByteMedia(item.getDescription().getMediaId(), BROWSED_ITEM_ID_INDEX);
+            }
+        }
+    }
+
     /* converts queue item received from getQueue call, to MediaItem used by FilterAttr method */
     private List<MediaBrowser.MediaItem> queueItem2MediaItem(
             List<MediaSession.QueueItem> tempItems) {
@@ -839,4 +1056,19 @@
 
         return encodedValue;
     }
+
+    public boolean isNumeric(String mediaId) {
+        String trimStr = mediaId.trim();
+        int length = trimStr.length();
+
+        for(int i = 0; i < length; i++) {
+            char c = trimStr.charAt(i);
+            if (!((c >= '0' && c <= '9'))) {
+                Log.v(TAG, "Non-Numeric media Id");
+                return false;
+            }
+        }
+        Log.v(TAG, "Numeric media Id");
+        return true;
+    }
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index 6111ec4..bf60f38 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -20,6 +20,7 @@
 import android.bluetooth.BluetoothAvrcpPlayerSettings;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothAvrcpController;
 import android.media.MediaDescription;
 import android.media.MediaMetadata;
@@ -27,25 +28,33 @@
 import android.media.browse.MediaBrowser.MediaItem;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
-import android.os.HandlerThread;
 import android.os.Message;
+import android.os.SystemProperties;
 import android.util.Log;
 
-import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
+import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.Utils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 import java.util.UUID;
 
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
 /**
  * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
  */
 public class AvrcpControllerService extends ProfileService {
     static final String TAG = "AvrcpControllerService";
-    static final boolean DBG = false;
-    static final boolean VDBG = false;
+    static final String LOG_TAG = "AvrcpController";
+    static final boolean DBG = true;
+    static final boolean VDBG = Log.isLoggable(LOG_TAG, Log.VERBOSE);
     /*
      *  Play State Values from JNI
      */
@@ -183,8 +192,8 @@
     public static final int BROWSE_SCOPE_SEARCH = 0x02;
     public static final int BROWSE_SCOPE_NOW_PLAYING = 0x03;
 
-    private AvrcpControllerStateMachine mAvrcpCtSm;
     private static AvrcpControllerService sAvrcpControllerService;
+    private AdapterService mAdapterService;
     // UID size is 8 bytes (AVRCP 1.6 spec)
     private static final byte[] EMPTY_UID = {0, 0, 0, 0, 0, 0, 0, 0};
 
@@ -196,6 +205,12 @@
     // (which also has no UID).
     private String mCurrentBrowseFolderUID = null;
 
+    // HashMap of state machine of AVRCP Controller Connection
+    private static final ConcurrentMap<BluetoothDevice,
+            AvrcpControllerStateMachine> mStateMachines = new ConcurrentHashMap<>();
+
+    private static int mMaxAllowedAvrcpConnection = 1;
+
     static {
         classInitNative();
     }
@@ -211,11 +226,13 @@
 
     @Override
     protected boolean start() {
-        HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
-        thread.start();
-        mAvrcpCtSm = new AvrcpControllerStateMachine(this);
-        mAvrcpCtSm.start();
+        Log.d(TAG, "start()");
 
+        mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
+                                "AdapterService cansinkstatenot be null when A2dpService starts");
+        mMaxAllowedAvrcpConnection = Math.min(
+                SystemProperties.getInt("persist.vendor.bt.a2dp.sink_conn", 1),
+                A2dpSinkService.MAX_ALLOWED_SINK_CONNECTIONS);
         setAvrcpControllerService(this);
         return true;
     }
@@ -223,8 +240,11 @@
     @Override
     protected boolean stop() {
         setAvrcpControllerService(null);
-        if (mAvrcpCtSm != null) {
-            mAvrcpCtSm.doQuit();
+        synchronized (mStateMachines) {
+            for (AvrcpControllerStateMachine mAvrcpCtSm : mStateMachines.values()) {
+                mAvrcpCtSm.doQuit();
+            }
+            mStateMachines.clear();
         }
         return true;
     }
@@ -253,8 +273,8 @@
     public synchronized List<BluetoothDevice> getConnectedDevices() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
-        if (mConnectedDevice != null) {
-            devices.add(mConnectedDevice);
+        for (AvrcpControllerStateMachine mAvrcpSm: mStateMachines.values()) {
+            devices.add(mAvrcpSm.getDevice());
         }
         return devices;
     }
@@ -264,33 +284,49 @@
      */
     public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
-        for (int i = 0; i < states.length; i++) {
-            if (states[i] == BluetoothProfile.STATE_CONNECTED && mConnectedDevice != null) {
-                devices.add(mConnectedDevice);
+        List<BluetoothDevice> devices = new ArrayList<>();
+        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+        synchronized (mStateMachines) {
+            for (BluetoothDevice device : bondedDevices) {
+                if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
+                                                 BluetoothUuid.AvrcpTarget)) {
+                    continue;
+                }
+                AvrcpControllerStateMachine sm = mStateMachines.get(device);
+                if (sm != null) {
+                    for (int i = 0; i < states.length; i++) {
+                        if (BluetoothProfile.STATE_CONNECTED == states[i]) {
+                            devices.add(device);
+                        }
+                    }
+                }
             }
+            return devices;
         }
-        return devices;
     }
 
     public synchronized int getConnectionState(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return (mConnectedDevice != null ? BluetoothProfile.STATE_CONNECTED
+        return (mStateMachines.get(device) != null ? BluetoothProfile.STATE_CONNECTED
                 : BluetoothProfile.STATE_DISCONNECTED);
     }
 
     public synchronized void sendGroupNavigationCmd(BluetoothDevice device, int keyCode,
             int keyState) {
-        Log.v(TAG, "sendGroupNavigationCmd keyCode: " + keyCode + " keyState: " + keyState);
+        Log.v(TAG, "sendGroupNavigationCmd keyCode: " + keyCode + " keyState: " + keyState
+                + ", device:" + device);
         if (device == null) {
             Log.e(TAG, "sendGroupNavigationCmd device is null");
         }
 
-        if (!(device.equals(mConnectedDevice))) {
-            Log.e(TAG, " Device does not match " + device + " connected " + mConnectedDevice);
+        if (!(mStateMachines.containsKey(device))) {
+            Log.e(TAG, " Device " + device + "does not match connected devices");
             return;
         }
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return;
         Message msg = mAvrcpCtSm.obtainMessage(
                 AvrcpControllerStateMachine.MESSAGE_SEND_GROUP_NAVIGATION_CMD,
                 keyCode, keyState, device);
@@ -298,30 +334,40 @@
     }
 
     public synchronized void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
-        Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState);
+        Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState
+                + ", To: " + device);
         if (device == null) {
-            Log.e(TAG, "sendPassThroughCmd Device is null");
+            Log.e(TAG, "sendPassThroughCmd: Device is null");
             return;
         }
 
-        if (!device.equals(mConnectedDevice)) {
-            Log.w(TAG, " Device does not match device " + device + " conn " + mConnectedDevice);
+        if (!mStateMachines.containsKey(device)) {
+            Log.e(TAG, " Device " + device + " does not match connected devices");
             return;
         }
 
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return;
         Message msg =
                 mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_SEND_PASS_THROUGH_CMD,
                         keyCode, keyState, device);
         mAvrcpCtSm.sendMessage(msg);
     }
 
-    public void startAvrcpUpdates() {
+    public void startAvrcpUpdates(BluetoothDevice device) {
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return;
         mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_START_METADATA_BROADCASTS)
                 .sendToTarget();
     }
 
-    public void stopAvrcpUpdates() {
+    public void stopAvrcpUpdates(BluetoothDevice device) {
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return;
         mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_STOP_METADATA_BROADCASTS)
                 .sendToTarget();
     }
@@ -336,9 +382,12 @@
             return null;
         }
 
-        if (!device.equals(mConnectedDevice)) {
+        if (!mStateMachines.containsKey(device)) {
             return null;
         }
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return null;
         return mAvrcpCtSm.getCurrentMetaData();
     }
 
@@ -358,12 +407,15 @@
             return null;
         }
 
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "Device " + device + " does not match connected deivce " + mConnectedDevice);
+        if (!mStateMachines.containsKey(device)) {
+            Log.e(TAG, "Device " + device + " does not match connected devices");
             return null;
 
         }
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return null;
         return mAvrcpCtSm.getCurrentPlayBackState(cached);
     }
 
@@ -377,8 +429,8 @@
             return null;
         }
 
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "device " + device + " does not match connected device " + mConnectedDevice);
+        if (!mStateMachines.containsKey(device)) {
+            Log.e(TAG, " Device " + device + "does not match connected devices");
             return null;
         }
 
@@ -420,8 +472,8 @@
             return false;
         }
 
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "getChildren device " + device + " does not match " + mConnectedDevice);
+        if (!mStateMachines.containsKey(device)) {
+            Log.e(TAG, "getChildren device " + device + " does not match connected devices");
             return false;
         }
 
@@ -429,6 +481,9 @@
             Log.e(TAG, "getChildren browse not yet connected");
             return false;
         }
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return false;
 
         if (!mAvrcpCtSm.isConnected()) {
             return false;
@@ -449,9 +504,9 @@
             return false;
         }
 
-        if (!device.equals(mConnectedDevice)) {
+        if (!mStateMachines.containsKey(device)) {
             Log.e(TAG,
-                    "getNowPlayingList device " + device + " does not match " + mConnectedDevice);
+                    "getNowPlayingList device " + device + " does not match connected devices");
             return false;
         }
 
@@ -461,7 +516,9 @@
         }
 
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return false;
         Message msg =
                 mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST,
                         start, items, id);
@@ -481,8 +538,8 @@
             return false;
         }
 
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "getFolderListing device " + device + " does not match " + mConnectedDevice);
+        if (!mStateMachines.containsKey(device)) {
+            Log.e(TAG, "getFolderListing device " + device + " does not match Connected Devices");
             return false;
         }
 
@@ -492,7 +549,9 @@
         }
 
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return false;
         Message msg =
                 mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start,
                         items, id);
@@ -511,8 +570,8 @@
             return false;
         }
 
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "getPlayerList device " + device + " does not match " + mConnectedDevice);
+        if (!mStateMachines.containsKey(device)) {
+            Log.e(TAG, "getPlayerList device " + device + " does not match Connected Devices");
             return false;
         }
 
@@ -522,7 +581,9 @@
         }
 
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return false;
         Message msg =
                 mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start,
                         items);
@@ -542,8 +603,8 @@
             return false;
         }
 
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "changeFolderPath device " + device + " does not match " + mConnectedDevice);
+        if (!mStateMachines.containsKey(device)) {
+            Log.e(TAG, "changeFolderPath device " + device + " does not match Connected Devices");
             return false;
         }
 
@@ -553,7 +614,9 @@
         }
 
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return false;
         Bundle b = new Bundle();
         b.putString(EXTRA_FOLDER_ID, fid);
         b.putString(EXTRA_FOLDER_BT_ID, uid);
@@ -574,8 +637,8 @@
             return false;
         }
 
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "changeFolderPath device " + device + " does not match " + mConnectedDevice);
+        if (!mStateMachines.containsKey(device)) {
+            Log.e(TAG, "changeFolderPath device " + device + " does not match Connected Devices");
             return false;
         }
 
@@ -585,7 +648,9 @@
         }
 
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return false;
         Message msg =
                 mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, id,
                         0, fid);
@@ -603,9 +668,9 @@
             return;
         }
 
-        if (!device.equals(mConnectedDevice)) {
+        if (!mStateMachines.containsKey(device)) {
             Log.e(TAG, "fetchAttrAndPlayItem device " + device + " does not match "
-                    + mConnectedDevice);
+                    + "Connected Devices");
             return;
         }
 
@@ -613,9 +678,67 @@
             Log.e(TAG, "fetchAttrAndPlayItem browse not yet connected");
             return;
         }
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return;
         mAvrcpCtSm.fetchAttrAndPlayItem(uid);
     }
 
+    // get state AvrcpControllerStateMachine corresponding to Bluetooth Device
+    public AvrcpControllerStateMachine getAvrcpCtStateMachine(BluetoothDevice device) {
+        if (device == null) {
+            return null;
+        }
+        AvrcpControllerStateMachine avrcpCtSm = mStateMachines.get(device);
+        if (avrcpCtSm == null) {
+            Log.d(TAG, "State Machine not found for device : " + device);
+         }
+        return avrcpCtSm;
+    }
+
+    // create state machine for new connection or return if already existing
+    public AvrcpControllerStateMachine getOrCreateAvrcpCtStateMachine(BluetoothDevice device) {
+        AvrcpControllerStateMachine avrcpCtSm = null;
+        synchronized (mStateMachines) {
+             avrcpCtSm = mStateMachines.get(device);
+            if (avrcpCtSm != null) {
+                Log.d(TAG, "State Machine is already present for device : " + device);
+                return avrcpCtSm;
+            }
+            if (mStateMachines.size() >= mMaxAllowedAvrcpConnection) {
+                Log.e(TAG, "Max Allowed AVRCP Connections already reached, Return.");
+                return null;
+            }
+            avrcpCtSm = new AvrcpControllerStateMachine(sAvrcpControllerService, device);
+            avrcpCtSm.start();
+            mStateMachines.put(device, avrcpCtSm);
+        }
+        return avrcpCtSm;
+    }
+
+    protected static void removeStateMachine(BluetoothDevice device) {
+        synchronized (mStateMachines) {
+            AvrcpControllerStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                Log.e(TAG, "State Machine already removed for device:" + device);
+                return;
+            }
+            Log.d(TAG, "State Machine removed for device:" + device);
+            mStateMachines.remove(device);
+            sm.doQuit();
+            sm = null;
+        }
+    }
+
+    // check if Browsing is connected for device
+    public boolean isBrowsingConnected(BluetoothDevice device) {
+        AvrcpControllerStateMachine avrcpCtSm = getAvrcpCtStateMachine(device);
+        if (avrcpCtSm == null) {
+            return false;
+        }
+        return avrcpCtSm.isBrowsingConnected();
+    }
+
     //Binder object: Must be static class or memory leak may occur
     private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
             implements IProfileServiceBinder {
@@ -731,32 +854,30 @@
     private synchronized void onConnectionStateChanged(boolean rcConnected, boolean brConnected,
             byte[] address) {
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        Log.d(TAG, "onConnectionStateChanged " + rcConnected + " " + brConnected + device
-                + " conn device " + mConnectedDevice);
+        Log.d(TAG, "onConnectionStateChanged: RC = " + rcConnected + ", BR = " + brConnected
+                + ", for: " + device);
         if (device == null) {
             Log.e(TAG, "onConnectionStateChanged Device is null");
             return;
         }
 
         // Adjust the AVRCP connection state.
-        int oldState = (device.equals(mConnectedDevice) ? BluetoothProfile.STATE_CONNECTED
+        int oldState = (mStateMachines.containsKey(device) ? BluetoothProfile.STATE_CONNECTED
                 : BluetoothProfile.STATE_DISCONNECTED);
         int newState = (rcConnected ? BluetoothProfile.STATE_CONNECTED
                 : BluetoothProfile.STATE_DISCONNECTED);
 
+        AvrcpControllerStateMachine mAvrcpCtSm = getOrCreateAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null) {
+            return;
+        }
+
         if (rcConnected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
-            /* AVRCPControllerService supports single connection */
-            if (mConnectedDevice != null) {
-                Log.d(TAG, "A Connection already exists, returning");
-                return;
-            }
-            mConnectedDevice = device;
             Message msg = mAvrcpCtSm.obtainMessage(
                     AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
                     oldState, device);
             mAvrcpCtSm.sendMessage(msg);
         } else if (!rcConnected && oldState == BluetoothProfile.STATE_CONNECTED) {
-            mConnectedDevice = null;
             Message msg = mAvrcpCtSm.obtainMessage(
                     AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
                     oldState, device);
@@ -776,11 +897,14 @@
     }
 
     // Called by JNI to notify Avrcp of features supported by the Remote device.
-    private void getRcFeatures(byte[] address, int features) {
+    private void getRcFeatures(byte[] address, int features, int caPsm) {
+        Log.i(TAG, " getRcFeatures caPsm :" + caPsm);
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        Message msg =
-                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES,
-                        features, 0, device);
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return;
+        Message msg = mAvrcpCtSm.obtainMessage(
+            AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES, features, caPsm, device);
         mAvrcpCtSm.sendMessage(msg);
     }
 
@@ -793,10 +917,13 @@
     private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) {
         Log.d(TAG, "handleRegisterNotificationAbsVol ");
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        if (device != null && !device.equals(mConnectedDevice)) {
+        if (device != null && !mStateMachines.containsKey(device)) {
             Log.e(TAG, "handleRegisterNotificationAbsVol device not found " + address);
             return;
         }
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return;
         Message msg = mAvrcpCtSm.obtainMessage(
                 AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION,
                 (int) label, 0);
@@ -807,10 +934,13 @@
     private synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) {
         Log.d(TAG, "handleSetAbsVolume ");
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        if (device != null && !device.equals(mConnectedDevice)) {
+        if (device != null && !mStateMachines.containsKey(device)) {
             Log.e(TAG, "handleSetAbsVolume device not found " + address);
             return;
         }
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return;
         Message msg = mAvrcpCtSm.obtainMessage(
                 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
         mAvrcpCtSm.sendMessage(msg);
@@ -823,7 +953,7 @@
             Log.d(TAG, "onTrackChanged");
         }
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        if (device != null && !device.equals(mConnectedDevice)) {
+        if (device != null && !mStateMachines.containsKey(device)) {
             Log.e(TAG, "onTrackChanged device not found " + address);
             return;
         }
@@ -837,11 +967,25 @@
         if (VDBG) {
             Log.d(TAG, "onTrackChanged " + trackInfo);
         }
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return;
         Message msg = mAvrcpCtSm.obtainMessage(
                 AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED, trackInfo);
         mAvrcpCtSm.sendMessage(msg);
     }
 
+    private void onElementAttributeUpdate(byte[] address, byte numAttributes, int[] attributes,
+            String[] attribVals) {
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return;
+        CoverArtUtils coverArtUtils= new CoverArtUtils();
+        coverArtUtils.onElementAttributeUpdate(address, numAttributes, attributes, attribVals,
+                device, mAvrcpCtSm);
+    }
+
     // Called by JNI periodically based upon timer to update play position
     private synchronized void onPlayPositionChanged(byte[] address, int songLen,
             int currSongPosition) {
@@ -849,10 +993,13 @@
             Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
         }
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        if (device != null && !device.equals(mConnectedDevice)) {
+        if (device != null && !mStateMachines.containsKey(device)) {
             Log.e(TAG, "onPlayPositionChanged not found device not found " + address);
             return;
         }
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return;
         Message msg = mAvrcpCtSm.obtainMessage(
                 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED,
                 songLen, currSongPosition);
@@ -865,7 +1012,7 @@
             Log.d(TAG, "onPlayStatusChanged " + playStatus);
         }
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        if (device != null && !device.equals(mConnectedDevice)) {
+        if (device != null && !mStateMachines.containsKey(device)) {
             Log.e(TAG, "onPlayStatusChanged not found device not found " + address);
             return;
         }
@@ -889,6 +1036,9 @@
             default:
                 playbackState = PlaybackState.STATE_NONE;
         }
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null)
+            return;
         Message msg = mAvrcpCtSm.obtainMessage(
                 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
         mAvrcpCtSm.sendMessage(msg);
@@ -901,7 +1051,7 @@
             Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
         }
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        if (device != null && !device.equals(mConnectedDevice)) {
+        if (device != null && !mStateMachines.containsKey(device)) {
             Log.e(TAG, "handlePlayerAppSetting not found device not found " + address);
             return;
         }
@@ -916,8 +1066,8 @@
             Log.d(TAG, "onPlayerAppSettingChanged ");
         }
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        if (device != null && !device.equals(mConnectedDevice)) {
-            Log.e(TAG, "onPlayerAppSettingChanged not found device not found " + address);
+        if (device != null && !mStateMachines.containsKey(device)) {
+            Log.e(TAG, "onPlayerAppSettingChanged: device not found " + address);
             return;
         }
         PlayerApplicationSettings desiredSettings =
@@ -926,12 +1076,19 @@
     }
 
     // Browsing related JNI callbacks.
-    void handleGetFolderItemsRsp(int status, MediaItem[] items) {
+    void handleGetFolderItemsRsp(int status, MediaItem[] items, byte[] address) {
         if (DBG) {
             Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + " items "
                     + items.length + " items.");
         }
 
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        Log.d(TAG, "handleGetFolderItemsRsp device:" + device);
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null) {
+            return;
+        }
+
         if (status == JNI_AVRC_INV_RANGE) {
             Log.w(TAG, "Sending out of range message.");
             // Send a special message since this could be used by state machine
@@ -956,9 +1113,15 @@
         mAvrcpCtSm.sendMessage(msg);
     }
 
-    void handleGetPlayerItemsRsp(AvrcpPlayer[] items) {
-        if (DBG) {
-            Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
+    void handleGetPlayerItemsRsp(AvrcpPlayer[] items, byte[] address) {
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+         if (DBG) {
+            Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length
+                    + " items. Device: " + device);
+        }
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null) {
+            return;
         }
         for (AvrcpPlayer item : items) {
             if (VDBG) {
@@ -1035,9 +1198,14 @@
         return player;
     }
 
-    private void handleChangeFolderRsp(int count) {
+    private void handleChangeFolderRsp(int count, byte[] address) {
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
         if (DBG) {
-            Log.d(TAG, "handleChangeFolderRsp count: " + count);
+            Log.d(TAG, "handleChangeFolderRsp count: " + count + ", device: " + device);
+        }
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null) {
+            return;
         }
         Message msg =
                 mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
@@ -1045,18 +1213,28 @@
         mAvrcpCtSm.sendMessage(msg);
     }
 
-    private void handleSetBrowsedPlayerRsp(int items, int depth) {
+    private void handleSetBrowsedPlayerRsp(int items, int depth, byte[] address) {
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
         if (DBG) {
-            Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
+            Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth + ", device: " + device);
+        }
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null) {
+            return;
         }
         Message msg = mAvrcpCtSm.obtainMessage(
                 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth);
         mAvrcpCtSm.sendMessage(msg);
     }
 
-    private void handleSetAddressedPlayerRsp(int status) {
+    private void handleSetAddressedPlayerRsp(int status, byte[] address) {
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
         if (DBG) {
-            Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
+            Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status + ", device :" + device);
+        }
+        AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+        if (mAvrcpCtSm == null) {
+            return;
         }
         Message msg = mAvrcpCtSm.obtainMessage(
                 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
@@ -1066,7 +1244,8 @@
     @Override
     public void dump(StringBuilder sb) {
         super.dump(sb);
-        mAvrcpCtSm.dump(sb);
+        for (AvrcpControllerStateMachine mAvrcpCtSm: mStateMachines.values())
+            mAvrcpCtSm.dump(sb);
     }
 
     public static String byteUIDToHexString(byte[] uid) {
@@ -1136,4 +1315,8 @@
     static native void setBrowsedPlayerNative(byte[] address, int playerId);
 
     static native void setAddressedPlayerNative(byte[] address, int playerId);
+
+    /* This api is used to fetch ElementAttributes */
+    native static void getElementAttributesNative(byte[] address, byte numAttributes,
+                                                   byte[] attribIds);
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index 3077664..0ac58d5 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -110,11 +110,12 @@
 
     private static final String TAG = "AvrcpControllerSM";
     private static final boolean DBG = true;
-    private static final boolean VDBG = false;
+    private static final boolean VDBG = AvrcpControllerService.VDBG;
 
     private final Context mContext;
     private final AudioManager mAudioManager;
-
+    private AvrcpControllerBipStateMachine mBipStateMachine;
+    private static CoverArtUtils mCoveArtUtils;
     private final State mDisconnected;
     private final State mConnected;
     private final SetBrowsedPlayer mSetBrowsedPlayer;
@@ -143,9 +144,14 @@
     // Browse tree.
     private BrowseTree mBrowseTree = new BrowseTree();
 
-    AvrcpControllerStateMachine(Context context) {
+    private final BluetoothDevice mDevice;
+    protected boolean mBrowsingConnected = false;
+    protected int prevState = -1;
+
+    AvrcpControllerStateMachine(Context context, BluetoothDevice device) {
         super(TAG);
         mContext = context;
+        mDevice = device;
 
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
@@ -175,15 +181,26 @@
         addState(mGetFolderList, mConnected);
         addState(mGetPlayerListing, mConnected);
         addState(mMoveToRoot, mConnected);
-
+        mCoveArtUtils = new CoverArtUtils();
         setInitialState(mDisconnected);
+        mBipStateMachine = AvrcpControllerBipStateMachine.make(this, getHandler(), context);
     }
 
     class Disconnected extends State {
 
         @Override
+        public void enter() {
+            Log.d(TAG, "Enter State: Disconnected mDevice: " + mDevice);
+            mBrowsingConnected = false;
+            if (prevState != -1) {
+                AvrcpControllerService.removeStateMachine(mDevice);
+           }
+        }
+
+        @Override
         public boolean processMessage(Message msg) {
-            if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
+            if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what)
+                    + ", mDevice: " + mDevice);
             switch (msg.what) {
                 case MESSAGE_PROCESS_CONNECTION_CHANGE:
                     if (msg.arg1 == BluetoothProfile.STATE_CONNECTED) {
@@ -215,6 +232,12 @@
             }
             return true;
         }
+
+        @Override
+        public void exit() {
+            prevState = BluetoothProfile.STATE_DISCONNECTED;
+            log("Exit State: Disconnected: ");
+        }
     }
 
     class Connected extends State {
@@ -331,6 +354,7 @@
 
                     case MESSAGE_PROCESS_CONNECTION_CHANGE:
                         if (msg.arg1 == BluetoothProfile.STATE_DISCONNECTED) {
+                            mCoveArtUtils.msgDisconnectBip(mBipStateMachine,mRemoteDevice);
                             synchronized (mLock) {
                                 mIsConnected = false;
                                 mRemoteDevice = null;
@@ -363,12 +387,14 @@
                         if (msg.arg1 == 1) {
                             intent.putExtra(BluetoothProfile.EXTRA_STATE,
                                     BluetoothProfile.STATE_CONNECTED);
+                            mBrowsingConnected = true;
                         } else if (msg.arg1 == 0) {
                             intent.putExtra(BluetoothProfile.EXTRA_STATE,
                                     BluetoothProfile.STATE_DISCONNECTED);
                             // If browse is disconnected, the next time we connect we should
                             // be at the ROOT.
                             mBrowseDepth = 0;
+                            mBrowsingConnected = false;
                         } else {
                             Log.w(TAG, "Incorrect browse state " + msg.arg1);
                         }
@@ -378,6 +404,10 @@
 
                     case MESSAGE_PROCESS_RC_FEATURES:
                         mRemoteDevice.setRemoteFeatures(msg.arg1);
+                        if (msg.arg2 > 0) {
+                            mCoveArtUtils.msgProcessRcFeatures
+                                (mBipStateMachine, mRemoteDevice,msg.arg2);
+                        }
                         break;
 
                     case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
@@ -435,8 +465,11 @@
                     case MESSAGE_PROCESS_TRACK_CHANGED:
                         // Music start playing automatically and update Metadata
                         mAddressedPlayer.updateCurrentTrack((TrackInfo) msg.obj);
-                        broadcastMetaDataChanged(
-                                mAddressedPlayer.getCurrentTrack().getMediaMetaData());
+                        if (mCoveArtUtils.msgTrackChanged(mContext, mBipStateMachine,
+                            mAddressedPlayer,mRemoteDevice)) {
+                            broadcastMetaDataChanged(
+                            mAddressedPlayer.getCurrentTrack().getMediaMetaData());
+                        }
                         break;
 
                     case MESSAGE_PROCESS_PLAY_POS_CHANGED:
@@ -458,12 +491,25 @@
                         }
                         break;
 
+                    case CoverArtUtils.MESSAGE_BIP_CONNECTED:
+                    case CoverArtUtils.MESSAGE_BIP_DISCONNECTED:
+                    case CoverArtUtils.MESSAGE_BIP_IMAGE_FETCHED:
+                    case CoverArtUtils.MESSAGE_BIP_THUMB_NAIL_FETCHED:
+                        mCoveArtUtils.processBipAction(mContext, mAddressedPlayer,
+                            mRemoteDevice, msg.what, msg);
+                        break;
                     default:
                         return false;
                 }
             }
             return true;
         }
+
+        @Override
+        public void exit() {
+            prevState = BluetoothProfile.STATE_CONNECTED;
+            log("Exit State: Connected: " + mDevice + ", currentMsg: " + getCurrentMessage().what);
+        }
     }
 
     // Handle the change folder path meta-action.
@@ -995,7 +1041,9 @@
             // If the receiver was never registered unregister will throw an
             // IllegalArgumentException.
         }
-        quit();
+        mCoveArtUtils.closeBip(mBipStateMachine);
+        // we should disacrd, all currently queuedup messages.
+        quitNow();
     }
 
     void dump(StringBuilder sb) {
@@ -1175,6 +1223,19 @@
     private void setAbsVolume(int absVol, int label) {
         int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
         int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        /* If SetAbsVolume Control Cmd is received from non-Streaming device then the
+         * requested volume level will not be set fot rendering and current Abs vol level
+         *  at DUT (sink: rendering device) will be sent in response. */
+        Log.d(TAG, "Streaming device: " + A2dpSinkService.getCurrentStreamingDevice()
+                + " Device:" + mDevice);
+        if (!mDevice.equals(A2dpSinkService.getCurrentStreamingDevice())) {
+            absVol = (currIndex * ABS_VOL_BASE) / maxVolume;
+            Log.w(TAG, "Volume change request came from non-streaming device," +
+                    "respond with current absVol: " + absVol);
+            AvrcpControllerService.sendAbsVolRspNative(mRemoteDevice.getBluetoothAddress(), absVol,
+                label);
+            return;
+        }
         // Ignore first volume command since phone may not know difference between stream volume
         // and amplifier volume.
         if (mRemoteDevice.getFirstAbsVolCmdRecvd()) {
@@ -1251,6 +1312,12 @@
             case MESSAGE_PROCESS_CONNECTION_CHANGE:
                 str = "CB_CONN_CHANGED";
                 break;
+            case CoverArtUtils.MESSAGE_BIP_CONNECTED:
+            case CoverArtUtils.MESSAGE_BIP_DISCONNECTED:
+            case CoverArtUtils.MESSAGE_BIP_IMAGE_FETCHED:
+            case CoverArtUtils.MESSAGE_BIP_THUMB_NAIL_FETCHED:
+                str = mCoveArtUtils.dumpMessageString(message);
+                break;
             default:
                 str = Integer.toString(message);
                 break;
@@ -1286,4 +1353,13 @@
         }
         return sb.toString();
     }
+
+    BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    boolean isBrowsingConnected() {
+        Log.d(TAG, "mBrowsingConnected = " + mBrowsingConnected);
+        return mBrowsingConnected;
+    }
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index 4482bd7..46addcf 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -41,7 +41,7 @@
 public class BrowseTree {
     private static final String TAG = "BrowseTree";
     private static final boolean DBG = false;
-    private static final boolean VDBG = false;
+    private static final boolean VDBG = AvrcpControllerService.VDBG;
 
     public static final int DIRECTION_DOWN = 0;
     public static final int DIRECTION_UP = 1;
diff --git a/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java b/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
index 7946747..07c2a4b 100644
--- a/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
+++ b/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
@@ -33,14 +33,17 @@
     private static final int FEAT_METADATA = 1;
     private static final int FEAT_ABSOLUTE_VOLUME = 2;
     private static final int FEAT_BROWSE = 4;
+    private static final int FEAT_COVER_ART = 8;
 
     private static final int VOLUME_LABEL_UNDEFINED = -1;
+    private static final int L2CAP_PSM_UNDEFINED = -1;
 
     final BluetoothDevice mBTDevice;
     private int mRemoteFeatures;
     private boolean mAbsVolNotificationRequested;
     private boolean mFirstAbsVolCmdRecvd;
     private int mNotificationLabel;
+    private int mBipL2capPsm;
 
     RemoteDevice(BluetoothDevice mDevice) {
         mBTDevice = mDevice;
@@ -48,13 +51,34 @@
         mAbsVolNotificationRequested = false;
         mNotificationLabel = VOLUME_LABEL_UNDEFINED;
         mFirstAbsVolCmdRecvd = false;
+        mBipL2capPsm = L2CAP_PSM_UNDEFINED;
     }
 
     synchronized void setRemoteFeatures(int remoteFeatures) {
         mRemoteFeatures = remoteFeatures;
     }
 
-    public synchronized byte[] getBluetoothAddress() {
+    synchronized void setRemoteBipPsm( int remotePsm) {
+        mBipL2capPsm = remotePsm;
+    }
+
+    synchronized int getRemoteBipPsm () {
+        return mBipL2capPsm;
+    }
+
+    synchronized boolean isBrowsingSupported() {
+        return ((mRemoteFeatures & FEAT_BROWSE) != 0);
+    }
+
+    synchronized boolean isMetaDataSupported() {
+        return ((mRemoteFeatures & FEAT_METADATA) != 0);
+    }
+
+    synchronized boolean isCoverArtSupported() {
+        return ((mRemoteFeatures & FEAT_COVER_ART) != 0);
+    }
+
+    synchronized public byte[] getBluetoothAddress() {
         return Utils.getByteAddress(mBTDevice);
     }
 
diff --git a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
index e89fb4c..f474495 100644
--- a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
+++ b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
@@ -17,7 +17,10 @@
 
 package com.android.bluetooth.avrcpcontroller;
 
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.media.MediaMetadata;
+import android.net.Uri;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -32,7 +35,7 @@
  */
 class TrackInfo {
     private static final String TAG = "AvrcpTrackInfo";
-    private static final boolean VDBG = false;
+    private static final boolean VDBG = AvrcpControllerService.VDBG;
 
     /*
      * Default values for each of the items from JNI
@@ -52,6 +55,7 @@
     private static final int MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER = 0x05;
     private static final int MEDIA_ATTRIBUTE_GENRE = 0x06;
     private static final int MEDIA_ATTRIBUTE_PLAYING_TIME = 0x07;
+    private static final int MEDIA_ATTRIBUTE_COVER_ART_HANDLE = 0x08;
 
 
     private final String mArtistName;
@@ -61,6 +65,9 @@
     private final long mTrackNum; // number of audio file on original recording.
     private final long mTotalTracks; // total number of tracks on original recording
     private final long mTrackLen; // full length of AudioFile.
+    private String mCoverArtHandle;
+    private String mImageLocation;
+    private String mThumbNailLocation;
 
     TrackInfo() {
         this(new ArrayList<Integer>(), new ArrayList<String>());
@@ -92,14 +99,36 @@
         attribute = attributeMap.get(MEDIA_ATTRIBUTE_PLAYING_TIME);
         mTrackLen = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute)
                 : TOTAL_TRACK_TIME_INVALID;
+        mCoverArtHandle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_COVER_ART_HANDLE,
+                UNPOPULATED_ATTRIBUTE);
+        mImageLocation = UNPOPULATED_ATTRIBUTE;
+        mThumbNailLocation = UNPOPULATED_ATTRIBUTE;
     }
 
-    @Override
+    boolean updateImageLocation(String mCAHandle, String mLocation) {
+        if (VDBG) Log.d(TAG, " updateImageLocation hndl " + mCAHandle + " location " + mLocation);
+        if (!mCAHandle.equals(mCoverArtHandle) || (mLocation == null)) {
+            return false;
+        }
+        mImageLocation = mLocation;
+        return true;
+    }
+
+    boolean updateThumbNailLocation(String mCAHandle, String mLocation) {
+        if (VDBG) Log.d(TAG, " mCAHandle " + mCAHandle + " location " + mLocation);
+        if (!mCAHandle.equals(mCoverArtHandle) || (mLocation == null)) {
+            return false;
+        }
+        mThumbNailLocation = mLocation;
+        return true;
+    }
+
     public String toString() {
         return "Metadata [artist=" + mArtistName + " trackTitle= " + mTrackTitle + " albumTitle= "
                 + mAlbumTitle + " genre= " + mGenre + " trackNum= " + Long.toString(mTrackNum)
                 + " track_len : " + Long.toString(mTrackLen) + " TotalTracks " + Long.toString(
-                mTotalTracks) + "]";
+                mTotalTracks) + " mCoverArtHandle=" + mCoverArtHandle +
+                " mImageLocation :"+mImageLocation+"]";
     }
 
     public MediaMetadata getMediaMetaData() {
@@ -114,6 +143,16 @@
         mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, mTrackNum);
         mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, mTotalTracks);
         mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION, mTrackLen);
+        if (mImageLocation != UNPOPULATED_ATTRIBUTE) {
+            Uri imageUri = Uri.parse(mImageLocation);
+            if (VDBG) Log.d(TAG," updating image uri = " + imageUri.toString());
+            mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+                    imageUri.toString());
+        }
+        if (mThumbNailLocation != UNPOPULATED_ATTRIBUTE) {
+            Bitmap mThumbNailBitmap = BitmapFactory.decodeFile(mThumbNailLocation);
+            mMetaDataBuilder.putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, mThumbNailBitmap);
+        }
         return mMetaDataBuilder.build();
     }
 
@@ -144,4 +183,15 @@
         }
         return sb.toString();
     }
+
+    String getCoverArtHandle() {
+        return mCoverArtHandle;
+    }
+
+    void clearCoverArtData() {
+        mCoverArtHandle = UNPOPULATED_ATTRIBUTE;
+        mImageLocation = UNPOPULATED_ATTRIBUTE;
+        mThumbNailLocation = UNPOPULATED_ATTRIBUTE;
+    }
+
 }
diff --git a/src/com/android/bluetooth/btservice/AbstractionLayer.java b/src/com/android/bluetooth/btservice/AbstractionLayer.java
index 6a2e636..597dc45 100644
--- a/src/com/android/bluetooth/btservice/AbstractionLayer.java
+++ b/src/com/android/bluetooth/btservice/AbstractionLayer.java
@@ -1,4 +1,39 @@
 /*
+ * Copyright (C) 2017, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted (subject to the limitations in the
+ * disclaimer below) provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer in the documentation and/or other materials provided
+ *     with the distribution.
+ *
+ * * Neither the name of The Linux Foundation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
+ * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
+ * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
  * Copyright (C) 2012 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -82,4 +117,26 @@
     public static final int BT_STATUS_RMT_DEV_DOWN = 10;
     public static final int BT_STATUS_AUTH_REJECTED = 11;
     public static final int BT_STATUS_AUTH_TIMEOUT = 12;
+
+    // Profile IDs to get profile features from profile_conf
+    public static final int AVRCP = 1;
+    public static final int PBAP = 2;
+    public static final int MAP = 3;
+
+    // Profile features supported in profile_conf
+    public static final int PROFILE_VERSION =1;
+    public static final int AVRCP_COVERART_SUPPORT = 2;
+    public static final int AVRCP_0103_SUPPORT = 3;
+    public static final int USE_SIM_SUPPORT = 4;
+    public static final int MAP_EMAIL_SUPPORT = 5;
+    public static final int PBAP_0102_SUPPORT = 6;
+
+    static final int BT_VENDOR_PROPERTY_TWS_PLUS_DEVICE_TYPE = 0x01;
+    static final int BT_VENDOR_PROPERTY_TWS_PLUS_PEER_ADDR = 0x02;
+    static final int BT_VENDOR_PROPERTY_TWS_PLUS_AUTO_CONNECT = 0x03;
+    static final int BT_VENDOR_PROPERTY_HOST_ADD_ON_FEATURES = 0x04;
+    static final int BT_VENDOR_PROPERTY_SOC_ADD_ON_FEATURES = 0x05;
+    static final int TWS_PLUS_DEV_TYPE_NONE = 0x00;
+    static final int TWS_PLUS_DEV_TYPE_PRIMARY = 0x01;
+    static final int TWS_PLUS_DEV_TYPE_SECONDARY = 0x02;
 }
diff --git a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
index e1f999d..ea414c5 100644
--- a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -39,6 +39,7 @@
 import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.hearingaid.HearingAidService;
 import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.ba.BATService;
 
 import java.util.LinkedList;
 import java.util.List;
@@ -190,6 +191,10 @@
                     Intent intent = (Intent) msg.obj;
                     BluetoothDevice device =
                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    if (device.getAddress().equals(BATService.mBAAddress)) {
+                        Log.d(TAG," Update from BA, bail out");
+                        break;
+                    }
                     int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
                     int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
                     if (prevState == nextState) {
@@ -222,8 +227,18 @@
                                     + "device " + device + " disconnected");
                         }
                         mA2dpConnectedDevices.remove(device);
+                        final A2dpService a2dpService = mFactory.getA2dpService();
+
                         if (Objects.equals(mA2dpActiveDevice, device)) {
-                            setA2dpActiveDevice(null);
+                            if (!mA2dpConnectedDevices.isEmpty() &&
+                                mAdapterService.isTwsPlusDevice(mA2dpConnectedDevices.get(0)) &&
+                                (a2dpService != null) &&
+                                (a2dpService.getConnectionState(mA2dpConnectedDevices.get(0)) ==
+                                         BluetoothProfile.STATE_CONNECTED)) {
+                                Log.d(TAG, "calling set a2dp Active dev: " + mA2dpConnectedDevices.get(0));
+                                setA2dpActiveDevice(mA2dpConnectedDevices.get(0));
+                            } else
+                                setA2dpActiveDevice(null);
                         }
                     }
                 }
@@ -280,9 +295,20 @@
                                     "handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): "
                                     + "device " + device + " disconnected");
                         }
+                        final HeadsetService hfpService = mFactory.getHeadsetService();
+
                         mHfpConnectedDevices.remove(device);
                         if (Objects.equals(mHfpActiveDevice, device)) {
-                            setHfpActiveDevice(null);
+                            if (!mHfpConnectedDevices.isEmpty() &&
+                               mAdapterService.isTwsPlusDevice(mHfpConnectedDevices.get(0)) &&
+                               (hfpService != null) &&
+                                (hfpService.getConnectionState(mHfpConnectedDevices.get(0)) ==
+                                         BluetoothProfile.STATE_CONNECTED)) {
+                               setHfpActiveDevice(mHfpConnectedDevices.get(0));
+                               Log.d(TAG, "calling set Active dev: " + mHfpConnectedDevices.get(0));
+                            } else {
+                               setHfpActiveDevice(null);
+                            }
                         }
                     }
                 }
diff --git a/src/com/android/bluetooth/btservice/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java
index d594850..a89aeef 100644
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -62,10 +62,14 @@
             "persist.bluetooth.maxconnectedaudiodevices";
     static final int MAX_CONNECTED_AUDIO_DEVICES_LOWER_BOND = 1;
     private static final int MAX_CONNECTED_AUDIO_DEVICES_UPPER_BOUND = 5;
-    private static final String A2DP_OFFLOAD_SUPPORTED_PROPERTY =
-            "ro.bluetooth.a2dp_offload.supported";
+    private static final int BLUETOOTH_NAME_MAX_LENGTH_BYTES = 248;
+
+    private static final String A2DP_OFFLOAD_ENABLE_PROPERTY =
+            "persist.bluetooth.a2dp_offload.enable";
     private static final String A2DP_OFFLOAD_DISABLED_PROPERTY =
             "persist.bluetooth.a2dp_offload.disabled";
+    private static final String A2DP_OFFLOAD_SUPPORTED_PROPERTY =
+            "ro.bluetooth.a2dp_offload.supported";
 
     private static final long DEFAULT_DISCOVERY_TIMEOUT_MS = 12800;
     private static final int BD_ADDR_LEN = 6; // in bytes
@@ -109,6 +113,36 @@
     private boolean mIsLeExtendedAdvertisingSupported;
     private boolean mIsLePeriodicAdvertisingSupported;
     private int mLeMaximumAdvertisingDataLength;
+    private boolean mWiPowerFastbootEnabled;
+    private boolean mSplitA2DPScrambleDataRequired;
+    private boolean mSplitA2DP44p1KhzSampleFreq;
+    private boolean mSplitA2DP48KhzSampleFreq;
+    private boolean mSplitA2DPSingleVSCommandSupport;
+    private boolean mSplitA2DPSourceSBCEncoding;
+    private boolean mSplitA2DPSourceSBC;
+    private boolean mSplitA2DPSourceMP3;
+    private boolean mSplitA2DPSourceAAC;
+    private boolean mSplitA2DPSourceLDAC;
+    private boolean mSplitA2DPSourceAPTX;
+    private boolean mSplitA2DPSourceAPTXHD;
+    private boolean mSplitA2DPSourceAPTXADAPTIVE;
+    private boolean mSplitA2DPSourceAPTXTWSPLUS;
+    private boolean mSplitA2DPSinkSBC;
+    private boolean mSplitA2DPSinkMP3;
+    private boolean mSplitA2DPSinkAAC;
+    private boolean mSplitA2DPSinkLDAC;
+    private boolean mSplitA2DPSinkAPTX;
+    private boolean mSplitA2DPSinkAPTXHD;
+    private boolean mSplitA2DPSinkAPTXADAPTIVE;
+    private boolean mSplitA2DPSinkAPTXTWSPLUS;
+    private boolean mVoiceDualSCO;
+    private boolean mVoiceTWSPLUSeSCOAG;
+    private boolean mSWBVoicewithAptxAdaptiveAG;
+    private boolean mBroadcastAudioTxwithEC_2_5;
+    private boolean mBroadcastAudioTxwithEC_3_9;
+    private boolean mBroadcastAudioRxwithEC_2_5;
+    private boolean mBroadcastAudioRxwithEC_3_9;
+    private boolean mAddonFeaturesSupported;
 
     private boolean mReceiverRegistered;
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -190,6 +224,12 @@
         // Make sure the final value of max connected audio devices is within allowed range
         mMaxConnectedAudioDevices = Math.min(Math.max(propertyOverlayedMaxConnectedAudioDevices,
                 MAX_CONNECTED_AUDIO_DEVICES_LOWER_BOND), MAX_CONNECTED_AUDIO_DEVICES_UPPER_BOUND);
+        // if QTI stack, overwrite max audio connections to 2
+        if(mService.isVendorIntfEnabled() && mMaxConnectedAudioDevices > 2) {
+            Log.i(TAG, "overwriting mMaxConnectedAudioDevices to 2 for vendor stack");
+            mMaxConnectedAudioDevices = 2;
+        }
+
         Log.i(TAG, "init(), maxConnectedAudioDevices, default="
                 + configDefaultMaxConnectedAudioDevices + ", propertyOverlayed="
                 + propertyOverlayedMaxConnectedAudioDevices + ", finalValue="
@@ -220,7 +260,9 @@
         mRemoteDevices = null;
         mProfileConnectionState.clear();
         if (mReceiverRegistered) {
-            mService.unregisterReceiver(mReceiver);
+            if (mReceiver != null) {
+                mService.unregisterReceiver(mReceiver);
+            }
             mReceiverRegistered = false;
         }
         mService = null;
@@ -245,6 +287,9 @@
      */
     boolean setName(String name) {
         synchronized (mObject) {
+            if (name.length() > BLUETOOTH_NAME_MAX_LENGTH_BYTES) {
+                name =  name.substring(0, BLUETOOTH_NAME_MAX_LENGTH_BYTES);
+            }
             return mService.setAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDNAME,
                     name.getBytes());
         }
@@ -447,6 +492,215 @@
     }
 
     /**
+     * @return Wipower Fastboot status
+     */
+    boolean isWipowerFastbootEnabled() {
+        return mWiPowerFastbootEnabled;
+    }
+
+    /**
+     * @return Split A2DP Scramble Data Support status
+     */
+    boolean isSplitA2DPScrambleDataRequired() {
+        return mSplitA2DPScrambleDataRequired;
+    }
+
+    /**
+     * @return Split A2DP 44.1Khz Sample Freq status
+     */
+    boolean isSplitA2DP44p1KhzSampleFreq() {
+        return mSplitA2DP44p1KhzSampleFreq;
+    }
+
+    /**
+     * @return Split A2DP 48Khz Sample Freq status
+     */
+    boolean isSplitA2DP48KhzSampleFreq() {
+        return mSplitA2DP48KhzSampleFreq;
+    }
+    /**
+     * @return Split A2DP Single VS Command Support status
+     */
+    boolean isSplitA2DPSingleVSCommandSupport() {
+        return mSplitA2DPSingleVSCommandSupport;
+    }
+
+    /**
+     * @return Split A2DP Source SBC Encoding status
+     */
+    boolean isSplitA2DPSourceSBCEncoding() {
+        return mSplitA2DPSourceSBCEncoding;
+    }
+
+    /**
+     * @return Split A2DP Source SBC status
+     */
+    boolean isSplitA2DPSourceSBC() {
+        return mSplitA2DPSourceSBC;
+    }
+
+    /**
+     * @return Split A2DP Source MP3 status
+     */
+    boolean isSplitA2DPSourceMP3() {
+        return mSplitA2DPSourceMP3;
+    }
+
+    /**
+     * @return Split A2DP Source AAC status
+     */
+    boolean isSplitA2DPSourceAAC() {
+        return mSplitA2DPSourceAAC;
+    }
+
+    /**
+     * @return Split A2DP Source LDAC status
+     */
+    boolean isSplitA2DPSourceLDAC() {
+        return mSplitA2DPSourceLDAC;
+    }
+
+    /**
+     * @return Split A2DP Source APTX status
+     */
+    boolean isSplitA2DPSourceAPTX() {
+        return mSplitA2DPSourceAPTX;
+    }
+
+    /**
+     * @return Split A2DP Source APTXHD status
+     */
+    boolean isSplitA2DPSourceAPTXHD() {
+        return mSplitA2DPSourceAPTXHD;
+    }
+
+    /**
+     * @return Split A2DP Source APTXADAPTIVE status
+     */
+    boolean isSplitA2DPSourceAPTXADAPTIVE() {
+        return mSplitA2DPSourceAPTXADAPTIVE;
+    }
+
+    /**
+     * @return Split A2DP Source APTXTWSPLUS status
+     */
+    boolean isSplitA2DPSourceAPTXTWSPLUS() {
+        return mSplitA2DPSourceAPTXTWSPLUS;
+    }
+
+    /**
+     * @return Split A2DP Sink SBC status
+     */
+    boolean isSplitA2DPSinkSBC() {
+        return mSplitA2DPSinkSBC;
+    }
+
+    /**
+     * @return Split A2DP Sink MP3 status
+     */
+    boolean isSplitA2DPSinkMP3() {
+        return mSplitA2DPSinkMP3;
+    }
+
+    /**
+     * @return Split A2DP Sink AAC status
+     */
+    boolean isSplitA2DPSinkAAC() {
+        return mSplitA2DPSinkAAC;
+    }
+
+    /**
+     * @return Split A2DP Sink LDAC status
+     */
+    boolean isSplitA2DPSinkLDAC() {
+        return mSplitA2DPSinkLDAC;
+    }
+
+    /**
+     * @return Split A2DP Sink APTX status
+     */
+    boolean isSplitA2DPSinkAPTX() {
+        return mSplitA2DPSinkAPTX;
+    }
+
+    /**
+     * @return Split A2DP Sink APTXHD status
+     */
+    boolean isSplitA2DPSinkAPTXHD() {
+        return mSplitA2DPSinkAPTXHD;
+    }
+
+    /**
+     * @return Split A2DP Sink APTXADAPTIVE status
+     */
+    boolean isSplitA2DPSinkAPTXADAPTIVE() {
+        return mSplitA2DPSinkAPTXADAPTIVE;
+    }
+
+    /**
+     * @return Split A2DP Sink APTXTWSPLUS status
+     */
+    boolean isSplitA2DPSinkAPTXTWSPLUS() {
+        return mSplitA2DPSinkAPTXTWSPLUS;
+    }
+
+    /**
+     * @return mVoiceDualSCO status
+     */
+    boolean isVoiceDualSCO() {
+        return mVoiceDualSCO;
+    }
+
+    /**
+     * @return Voice TWS+ eSCO AG status
+     */
+    boolean isVoiceTWSPLUSeSCOAG() {
+        return mVoiceTWSPLUSeSCOAG;
+    }
+
+    /**
+     * @return SWB Voice with Aptx Adaptive AG status
+     */
+    boolean isSWBVoicewithAptxAdaptiveAG() {
+        return mSWBVoicewithAptxAdaptiveAG;
+    }
+
+    /**
+     * @return Broadcast Audio Tx with EC_2_5 status
+     */
+    boolean isBroadcastAudioTxwithEC_2_5() {
+        return mBroadcastAudioTxwithEC_2_5;
+    }
+
+    /**
+     * @return Broadcast Audio Tx with EC_3_9 status
+     */
+    boolean isBroadcastAudioTxwithEC_3_9() {
+        return mBroadcastAudioTxwithEC_3_9;
+    }
+
+    /**
+     * @return Broadcast Audio Rx with EC_2_5 status
+     */
+    boolean isBroadcastAudioRxwithEC_2_5() {
+        return mBroadcastAudioRxwithEC_2_5;
+    }
+
+    /**
+     * @return Broadcast Audio Rx with EC_3_9 status
+     */
+    boolean isBroadcastAudioRxwithEC_3_9() {
+        return mBroadcastAudioRxwithEC_3_9;
+    }
+
+    /**
+     * @return Broadcast AddonFeatures Cmd Support status
+     */
+    boolean isAddonFeaturesCmdSupported() {
+        return mAddonFeaturesSupported;
+    }
+
+    /**
      * @return the mBondedDevices
      */
     BluetoothDevice[] getBondedDevices() {
@@ -555,10 +809,22 @@
             return;
         }
 
+
         synchronized (mObject) {
+
             updateProfileConnectionState(profile, state, prevState);
 
-            if (updateCountersAndCheckForConnectionStateChange(state, prevState)) {
+            boolean validateConnectionState = false;
+
+            try {
+                validateConnectionState =
+                   updateCountersAndCheckForConnectionStateChange(state, prevState);
+            } catch (IllegalStateException ee) {
+                Log.w(TAG, "ADAPTER_CONNECTION_STATE_CHANGE: unexpected transition for profile="
+                        + profile + ", " + prevState + " -> " + state);
+            }
+
+            if (validateConnectionState) {
                 int newAdapterState = convertToAdapterState(state);
                 int prevAdapterState = convertToAdapterState(prevState);
                 setConnectionState(newAdapterState);
@@ -834,6 +1100,78 @@
                 + " mLeMaximumAdvertisingDataLength = " + mLeMaximumAdvertisingDataLength);
     }
 
+    public void updateSocFeatureSupport(byte[] val) {
+        mAddonFeaturesSupported = (val.length != 0);
+        if (!mAddonFeaturesSupported) {
+            Log.d(TAG, "BT_PROPERTY_ADD_ON_FEATURES: add-on features VSC is not supported");
+        } else {
+            mWiPowerFastbootEnabled = ((0x01 & ((int) val[0])) != 0);
+            mSplitA2DPScrambleDataRequired = ((0x02 & ((int) val[0])) != 0);
+            mSplitA2DP44p1KhzSampleFreq = ((0x04 & ((int) val[0])) != 0);
+            mSplitA2DP48KhzSampleFreq = ((0x08 & ((int) val[0])) != 0);
+            mSplitA2DPSingleVSCommandSupport = ((0x10 & ((int) val[0])) != 0);
+            mSplitA2DPSourceSBCEncoding = ((0x20 & ((int) val[0])) != 0);
+            mSplitA2DPSourceSBC = ((0x01 & ((int) val[1])) != 0);
+            mSplitA2DPSourceMP3 = ((0x02 & ((int) val[1])) != 0);
+            mSplitA2DPSourceAAC = ((0x04 & ((int) val[1])) != 0);
+            mSplitA2DPSourceLDAC = ((0x08 & ((int) val[1])) != 0);
+            mSplitA2DPSourceAPTX = ((0x10 & ((int) val[1])) != 0);
+            mSplitA2DPSourceAPTXHD = ((0x20 & ((int) val[1])) != 0);
+            mSplitA2DPSourceAPTXADAPTIVE = ((0x40 & ((int) val[1])) != 0);
+            mSplitA2DPSourceAPTXTWSPLUS = ((0x80 & ((int) val[1])) != 0);
+            mSplitA2DPSinkSBC = ((0x01 & ((int) val[2])) != 0);
+            mSplitA2DPSinkMP3 = ((0x02 & ((int) val[2])) != 0);
+            mSplitA2DPSinkAAC = ((0x04 & ((int) val[2])) != 0);
+            mSplitA2DPSinkLDAC = ((0x08 & ((int) val[2])) != 0);
+            mSplitA2DPSinkAPTX = ((0x10 & ((int) val[2])) != 0);
+            mSplitA2DPSinkAPTXHD = ((0x20 & ((int) val[2])) != 0);
+            mSplitA2DPSinkAPTXADAPTIVE = ((0x40 & ((int) val[2])) != 0);
+            mSplitA2DPSinkAPTXTWSPLUS = ((0x80 & ((int) val[2])) != 0);
+            mVoiceDualSCO = ((0x01 & ((int) val[3])) != 0);
+            mVoiceTWSPLUSeSCOAG = ((0x02 & ((int) val[3])) != 0);
+            mSWBVoicewithAptxAdaptiveAG = ((0x04 & ((int) val[3])) != 0);
+            mBroadcastAudioTxwithEC_2_5 = ((0x01 & ((int) val[4])) != 0);
+            mBroadcastAudioTxwithEC_3_9 = ((0x02 & ((int) val[4])) != 0);
+            mBroadcastAudioRxwithEC_2_5 = ((0x04 & ((int) val[4])) != 0);
+            mBroadcastAudioRxwithEC_3_9 = ((0x08 & ((int) val[4])) != 0);
+
+
+            Log.d(TAG, "BT_PROPERTY_ADD_ON_FEATURES: update from BT controller"
+                    + "\n mWiPowerFastbootEnabled = "
+                    + mWiPowerFastbootEnabled + "\n SplitA2DPScrambleDataRequired = "
+                    + mSplitA2DPScrambleDataRequired + "\n mSplitA2DP44p1KhzSampleFreq = "
+                    + mSplitA2DP44p1KhzSampleFreq + "\n mSplitA2DP48KhzSampleFreq = "
+                    + mSplitA2DP48KhzSampleFreq + "\n mSplitA2DPSingleVSCommandSupport = "
+                    + mSplitA2DPSingleVSCommandSupport + "\n mSplitA2DPSourceSBCEncoding = "
+                    + mSplitA2DPSourceSBCEncoding + "\n mSplitA2DPSourceSBC = "
+                    + mSplitA2DPSourceSBC + "\n mSplitA2DPSourceMP3 = "
+                    + mSplitA2DPSourceMP3 + "\n mSplitA2DPSourceAAC = "
+                    + mSplitA2DPSourceAAC + "\n mSplitA2DPSourceLDAC = " + mSplitA2DPSourceLDAC
+                    + "\n mSplitA2DPSourceAPTX = " + mSplitA2DPSourceAPTX
+                    + "\n mSplitA2DPSourceAPTXHD = " + mSplitA2DPSourceAPTXHD
+                    + "\n mSplitA2DPSourceAPTXADAPTIVE = " + mSplitA2DPSourceAPTXADAPTIVE
+                    + "\n mSplitA2DPSourceAPTXTWSPLUS = " + mSplitA2DPSourceAPTXTWSPLUS
+                    + "\n mSplitA2DPSinkSBC = " + mSplitA2DPSinkSBC + "\n mSplitA2DPSinkMP3 = "
+                    + mSplitA2DPSinkMP3 + "\n mSplitA2DPSinkAAC = "
+                    + mSplitA2DPSinkAAC + "\n mSplitA2DPSinkLDAC = " + mSplitA2DPSinkLDAC
+                    + "\n mSplitA2DPSinkAPTX = " + mSplitA2DPSinkAPTX
+                    + "\n mSplitA2DPSinkAPTXHD = " + mSplitA2DPSinkAPTXHD
+                    + "\n mSplitA2DPSinkAPTXADAPTIVE = " + mSplitA2DPSinkAPTXADAPTIVE
+                    + "\n mSplitA2DPSinkAPTXTWSPLUS = " + mSplitA2DPSinkAPTXTWSPLUS
+                    + "\n mVoiceDualSCO = " + mVoiceDualSCO + "\n mVoiceTWSPLUSeSCOAG = "
+                    + mVoiceTWSPLUSeSCOAG + "\n mSWBVoicewithAptxAdaptiveAG = "
+                    + mSWBVoicewithAptxAdaptiveAG + "\n BroadcastAudioTxwithEC_2_5 = "
+                    + mBroadcastAudioTxwithEC_2_5 + "\n mBroadcastAudioTxwithEC_3_9 = "
+                    + mBroadcastAudioTxwithEC_3_9 + "\n mBroadcastAudioRxwithEC_2_5 = "
+                    + mBroadcastAudioRxwithEC_2_5 + "\n mBroadcastAudioRxwithEC_3_9= "
+                    + mBroadcastAudioRxwithEC_3_9);
+         }
+    }
+
+    public void updateHostFeatureSupport(byte[] val) {
+         Log.d(TAG, " Host Features are not supported currently ");
+    }
+
     void onBluetoothReady() {
         debugLog("onBluetoothReady, state=" + BluetoothAdapter.nameForState(getState())
                 + ", ScanMode=" + mScanMode);
@@ -872,7 +1210,7 @@
         infoLog("Callback:discoveryStateChangeCallback with state:" + state);
         synchronized (mObject) {
             Intent intent;
-            if (state == AbstractionLayer.BT_DISCOVERY_STOPPED) {
+            if ((state == AbstractionLayer.BT_DISCOVERY_STOPPED) && mDiscovering) {
                 mDiscovering = false;
                 mDiscoveryEndMs = System.currentTimeMillis();
                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index ad78955..2b3db26 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -1,4 +1,39 @@
 /*
+ * Copyright (C) 2017, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted (subject to the limitations in the
+ * disclaimer below) provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer in the documentation and/or other materials provided
+ *     with the distribution.
+ *
+ * * Neither the name of The Linux Foundation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
+ * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
+ * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
  * Copyright (C) 2012 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -68,9 +103,13 @@
 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
 import com.android.bluetooth.gatt.GattService;
 import com.android.bluetooth.sdp.SdpManager;
+import com.android.bluetooth.ba.BATService;
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
+import android.net.wifi.WifiManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
 
 import com.google.protobuf.InvalidProtocolBufferException;
 
@@ -96,6 +135,7 @@
     private long mRxTimeTotalMs;
     private long mIdleTimeTotalMs;
     private long mEnergyUsedTotalVoltAmpSecMicro;
+    private WifiManager mWifiManager;
     private final SparseArray<UidTraffic> mUidTraffic = new SparseArray<>();
 
     private final ArrayList<ProfileService> mRegisteredProfiles = new ArrayList<>();
@@ -130,6 +170,7 @@
     private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
 
     static {
+        System.loadLibrary("bluetooth_jni");
         classInitNative();
     }
 
@@ -156,6 +197,8 @@
 
     private AdapterProperties mAdapterProperties;
     private AdapterState mAdapterStateMachine;
+    private Vendor mVendor;
+    private boolean mVendorAvailble;
     private BondStateMachine mBondStateMachine;
     private JniCallbacks mJniCallbacks;
     private RemoteDevices mRemoteDevices;
@@ -178,10 +221,12 @@
     private PowerManager.WakeLock mWakeLock;
     private String mWakeLockName;
     private UserManager mUserManager;
+    private static BluetoothAdapter mAdapter;
 
     private ProfileObserver mProfileObserver;
     private PhonePolicy mPhonePolicy;
     private ActiveDeviceManager mActiveDeviceManager;
+    private VendorSocket mVendorSocket;
 
     /**
      * Register a {@link ProfileService} with AdapterService.
@@ -217,6 +262,40 @@
         mHandler.sendMessage(m);
     }
 
+    public boolean getProfileInfo(int profile_id , int profile_info) {
+        if (isVendorIntfEnabled()) {
+            return mVendor.getProfileInfo(profile_id, profile_info);
+        } else {
+            return false;
+        }
+    }
+
+    private void fetchWifiState() {
+        if (!isVendorIntfEnabled()) {
+            return;
+        }
+        ConnectivityManager connMgr =
+              (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+        if (networkInfo.isConnected()) {
+            mVendor.setWifiState(true);
+        } else {
+            mVendor.setWifiState(false);
+        }
+    }
+
+    public void StartHCIClose() {
+        if (isVendorIntfEnabled()) {
+            mVendor.HCIClose();
+        }
+    }
+
+     public void voipNetworkWifiInfo(boolean isVoipStarted, boolean isNetworkWifi) {
+        Log.i(TAG, "In voipNetworkWifiInfo, isVoipStarted: " + isVoipStarted +
+                    ", isNetworkWifi: " + isNetworkWifi);
+        mVendor.voipNetworkWifiInformation(isVoipStarted, isNetworkWifi);
+    }
+
     private static final int MESSAGE_PROFILE_SERVICE_STATE_CHANGED = 1;
     private static final int MESSAGE_PROFILE_SERVICE_REGISTERED = 2;
     private static final int MESSAGE_PROFILE_SERVICE_UNREGISTERED = 3;
@@ -271,13 +350,17 @@
                     }
                     mRunningProfiles.add(profile);
                     if (GattService.class.getSimpleName().equals(profile.getName())) {
+                        Log.w(TAG,"onProfileServiceStateChange() - Gatt profile service started..");
                         enableNativeWithGuestFlag();
                     } else if (mRegisteredProfiles.size() == Config.getSupportedProfiles().length
                             && mRegisteredProfiles.size() == mRunningProfiles.size()) {
+                        Log.w(TAG,"onProfileServiceStateChange() - All profile services started..");
                         mAdapterProperties.onBluetoothReady();
                         updateUuids();
                         setBluetoothClassFromConfig();
                         mAdapterStateMachine.sendMessage(AdapterState.BREDR_STARTED);
+                        //update wifi state to lower layers
+                        fetchWifiState();
                     }
                     break;
                 case BluetoothAdapter.STATE_OFF:
@@ -293,9 +376,10 @@
                     // If only GATT is left, send BREDR_STOPPED.
                     if ((mRunningProfiles.size() == 1 && (GattService.class.getSimpleName()
                             .equals(mRunningProfiles.get(0).getName())))) {
+                        Log.w(TAG,"onProfileServiceStateChange() - All profile services except gatt stopped..");
                         mAdapterStateMachine.sendMessage(AdapterState.BREDR_STOPPED);
                     } else if (mRunningProfiles.size() == 0) {
-                        disableNative();
+                        Log.w(TAG,"onProfileServiceStateChange() - All profile services stopped..");
                         mAdapterStateMachine.sendMessage(AdapterState.BLE_STOPPED);
                     }
                     break;
@@ -370,12 +454,15 @@
     public void onCreate() {
         super.onCreate();
         debugLog("onCreate()");
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
         mRemoteDevices = new RemoteDevices(this, Looper.getMainLooper());
         mRemoteDevices.init();
         mBinder = new AdapterServiceBinder(this);
         mAdapterProperties = new AdapterProperties(this);
-        mAdapterStateMachine = AdapterState.make(this);
+        mVendor = new Vendor(this);
+        mAdapterStateMachine =  AdapterState.make(this);
         mJniCallbacks = new JniCallbacks(this, mAdapterProperties);
+        mVendorSocket = new VendorSocket(this);
         initNative();
         mNativeAvailable = true;
         mCallbacks = new RemoteCallbackList<IBluetoothCallback>();
@@ -403,6 +490,7 @@
         } else {
             Log.i(TAG, "Phone policy disabled");
         }
+        mBondStateMachine = BondStateMachine.make(mPowerManager, this, mAdapterProperties, mRemoteDevices);
 
         mActiveDeviceManager = new ActiveDeviceManager(this, new ServiceFactory());
         mActiveDeviceManager.start();
@@ -423,6 +511,9 @@
                 return null;
             }
         }.execute();
+        mVendor.init();
+        mVendorAvailble = mVendor.getQtiStackStatus();
+        mVendorSocket.init();
 
         try {
             int systemUiUid = getApplicationContext().getPackageManager().getPackageUidAsUser(
@@ -441,6 +532,18 @@
         int fuid = ActivityManager.getCurrentUser();
         Utils.setForegroundUserId(fuid);
         setForegroundUserIdNative(fuid);
+
+        // Reset |mRemoteDevices| whenever BLE is turned off then on
+        // This is to replace the fact that |mRemoteDevices| was
+        // reinitialized in previous code.
+        //
+        // TODO(apanicke): The reason is unclear but
+        // I believe it is to clear the variable every time BLE was
+        // turned off then on. The same effect can be achieved by
+        // calling cleanup but this may not be necessary at all
+        // We should figure out why this is needed later
+        mRemoteDevices.reset();
+        mAdapterProperties.init(mRemoteDevices);
     }
 
     @Override
@@ -451,7 +554,7 @@
 
     @Override
     public boolean onUnbind(Intent intent) {
-        debugLog("onUnbind() - calling cleanup");
+        Log.w(TAG, "onUnbind, calling cleanup");
         cleanup();
         return super.onUnbind(intent);
     }
@@ -480,26 +583,16 @@
 
     void bringUpBle() {
         debugLog("bleOnProcessStart()");
-
+        /* To reload profile support in BLE turning ON state. So even if profile support
+         *  is disabled in Bluetooth Adapter turned off state(10), this flag will ensure
+         *  rechecking profile support after BT is turned ON
+         */
         if (getResources().getBoolean(
-                R.bool.config_bluetooth_reload_supported_profiles_when_enabled)) {
+                com.android.bluetooth.R.bool.reload_supported_profiles_when_enabled)) {
             Config.init(getApplicationContext());
         }
 
-        // Reset |mRemoteDevices| whenever BLE is turned off then on
-        // This is to replace the fact that |mRemoteDevices| was
-        // reinitialized in previous code.
-        //
-        // TODO(apanicke): The reason is unclear but
-        // I believe it is to clear the variable every time BLE was
-        // turned off then on. The same effect can be achieved by
-        // calling cleanup but this may not be necessary at all
-        // We should figure out why this is needed later
-        mRemoteDevices.reset();
-        mAdapterProperties.init(mRemoteDevices);
-
-        debugLog("bleOnProcessStart() - Make Bond State Machine");
-        mBondStateMachine = BondStateMachine.make(this, mAdapterProperties, mRemoteDevices);
+        debugLog("BleOnProcessStart() - Make Bond State Machine");
 
         mJniCallbacks.init(mBondStateMachine, mRemoteDevices);
 
@@ -522,6 +615,7 @@
     void stateChangeCallback(int status) {
         if (status == AbstractionLayer.BT_STATE_OFF) {
             debugLog("stateChangeCallback: disableNative() completed");
+            mAdapterStateMachine.sendMessage(AdapterState.STACK_DISABLED);
         } else if (status == AbstractionLayer.BT_STATE_ON) {
             mAdapterStateMachine.sendMessage(AdapterState.BLE_STARTED);
         } else {
@@ -529,6 +623,12 @@
         }
     }
 
+    void ssrCleanupCallback() {
+        disableProfileServices(false);
+        Log.e(TAG, "Killing the process to force restart as part of fault tolerance");
+        android.os.Process.killProcess(android.os.Process.myPid());
+    }
+
     /**
      * Sets the Bluetooth CoD value of the local adapter if there exists a config value for it.
      */
@@ -555,6 +655,10 @@
         return result;
     }
 
+    void startBluetoothDisable() {
+        mAdapterStateMachine.sendMessage(AdapterState.BEGIN_BREDR_STOP);
+    }
+
     void startProfileServices() {
         debugLog("startCoreServices()");
         Class[] supportedProfileServices = Config.getSupportedProfiles();
@@ -569,8 +673,17 @@
         }
     }
 
-    void stopProfileServices() {
+    void startBrEdrCleanup(){
         mAdapterProperties.onBluetoothDisable();
+        if (isVendorIntfEnabled()) {
+            mVendor.bredrCleanup();
+        } else {
+            mAdapterStateMachine.sendMessage(
+            mAdapterStateMachine.obtainMessage(AdapterState.BEGIN_BREDR_STOP));
+        }
+    }
+
+    void stopProfileServices() {
         Class[] supportedProfileServices = Config.getSupportedProfiles();
         if (supportedProfileServices.length == 1 && (mRunningProfiles.size() == 1
                 && GattService.class.getSimpleName().equals(mRunningProfiles.get(0).getName()))) {
@@ -581,6 +694,27 @@
         }
     }
 
+    void disableProfileServices(boolean onlyGatt) {
+        Class[] services = Config.getSupportedProfiles();
+        for (int i = 0; i < services.length; i++) {
+            if (onlyGatt && !(GattService.class.getSimpleName().equals(services[i].getSimpleName())))
+                continue;
+            boolean res = false;
+            String serviceName = services[i].getName();
+            mProfileServicesState.put(serviceName,BluetoothAdapter.STATE_OFF);
+            Intent intent = new Intent(this,services[i]);
+            intent.putExtra(EXTRA_ACTION,ACTION_SERVICE_STATE_CHANGED);
+            intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            res = stopService(intent);
+            Log.d(TAG, "disableProfileServices() - Stopping service "
+                + serviceName + " with result: " + res);
+            if(onlyGatt)
+                break;
+        }
+        return;
+    }
+
     private void stopGattProfileService() {
         mAdapterProperties.onBleDisable();
         if (mRunningProfiles.size() == 0) {
@@ -626,6 +760,12 @@
             return;
         }
 
+        // Unregistering Bluetooth Adapter
+        if ( mAdapter!= null ){
+            mAdapter.unregisterAdapter();
+            mAdapter = null;
+        }
+
         clearAdapterService(this);
 
         mCleaningUp = true;
@@ -675,6 +815,14 @@
             mAdapterProperties.cleanup();
         }
 
+        if (mVendor != null) {
+            mVendor.cleanup();
+        }
+
+        if (mVendorSocket!= null) {
+            mVendorSocket.cleanup();
+        }
+
         if (mJniCallbacks != null) {
             mJniCallbacks.cleanup();
         }
@@ -705,6 +853,7 @@
         Intent intent = new Intent(this, service);
         intent.putExtra(EXTRA_ACTION, ACTION_SERVICE_STATE_CHANGED);
         intent.putExtra(BluetoothAdapter.EXTRA_STATE, state);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         startService(intent);
     }
 
@@ -1395,6 +1544,28 @@
             return service.sdpSearch(device, uuid);
         }
 
+        public boolean isTwsPlusDevice(BluetoothDevice device) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG,"(): isTws+device(): not allowed for non-active user");
+                return false;
+            }
+
+            AdapterService service = getService();
+            if (service == null) return false;
+            return service.isTwsPlusDevice(device);
+        }
+
+        public String getTwsPlusPeerAddress(BluetoothDevice device) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG,"getTws+peerAddress(): not allowed for non-active user");
+                return null;
+            }
+
+            AdapterService service = getService();
+            if (service == null) return null;
+            return service.getTwsPlusPeerAddress(device);
+        }
+
         @Override
         public int getBatteryLevel(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
@@ -1435,9 +1606,13 @@
             if (service == null) {
                 return false;
             }
-            service.disable();
+            if ((getState() == BluetoothAdapter.STATE_BLE_ON) ||
+                (getState() == BluetoothAdapter.STATE_BLE_TURNING_ON)) {
+                service.onBrEdrDown();
+            } else {
+                service.disable();
+            }
             return service.factoryReset();
-
         }
 
         @Override
@@ -1567,6 +1742,16 @@
         }
 
         @Override
+        public void updateQuietModeStatus(boolean quietMode) {
+            AdapterService service = getService();
+            if (service == null) {
+                return;
+            }
+            service.updateQuietModeStatus(quietMode);
+        }
+
+
+        @Override
         public void onBrEdrDown() {
             AdapterService service = getService();
             if (service == null) {
@@ -1575,6 +1760,29 @@
             service.onBrEdrDown();
         }
 
+        public int setSocketOpt(int type, int channel, int optionName, byte [] optionVal,
+                                                    int optionLen) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG,"setSocketOpt(): not allowed for non-active user");
+                return -1;
+            }
+
+            AdapterService service = getService();
+            if (service == null) return -1;
+            return service.setSocketOpt(type, channel, optionName, optionVal, optionLen);
+        }
+
+        public int getSocketOpt(int type, int channel, int optionName, byte [] optionVal) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG,"getSocketOpt(): not allowed for non-active user");
+                return -1;
+            }
+
+            AdapterService service = getService();
+            if (service == null) return -1;
+            return service.getSocketOpt(type, channel, optionName, optionVal);
+        }
+
         @Override
         public void dump(FileDescriptor fd, String[] args) {
             PrintWriter writer = new PrintWriter(new FileOutputStream(fd));
@@ -1596,6 +1804,10 @@
         return mAdapterProperties.getState() == BluetoothAdapter.STATE_ON;
     }
 
+    public boolean isVendorIntfEnabled() {
+        return mVendorAvailble;
+    }
+
     public int getState() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (mAdapterProperties != null) {
@@ -1690,6 +1902,74 @@
         return result && storeBluetoothClassConfig(bluetoothClass.getClassOfDevice());
     }
 
+    boolean setTwsPlusDevType(byte[] address, short twsPlusDevType) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        BluetoothDevice device = mRemoteDevices.getDevice(address);
+        DeviceProperties deviceProp;
+        if (device == null) {
+            deviceProp = mRemoteDevices.addDeviceProperties(address);
+        } else {
+            deviceProp = mRemoteDevices.getDeviceProperties(device);
+        }
+        if(deviceProp != null) {
+            deviceProp.setTwsPlusDevType(twsPlusDevType);
+            return true;
+        }
+        return false;
+    }
+
+    boolean setTwsPlusPeerEbAddress(byte[] address, byte[] peerEbAddress) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        BluetoothDevice device = mRemoteDevices.getDevice(address);
+        BluetoothDevice peerDevice = null;
+        DeviceProperties deviceProp;
+        DeviceProperties peerDeviceProp;
+
+        if(peerEbAddress != null)
+            peerDevice = mRemoteDevices.getDevice(peerEbAddress);
+
+        if (device == null) {
+            deviceProp = mRemoteDevices.addDeviceProperties(address);
+        } else {
+            deviceProp = mRemoteDevices.getDeviceProperties(device);
+        }
+        if (peerDevice == null && peerEbAddress != null) {
+            peerDeviceProp = mRemoteDevices.addDeviceProperties(peerEbAddress);
+            peerDevice = mRemoteDevices.getDevice(peerEbAddress);
+        }
+        if(deviceProp != null) {
+            deviceProp.setTwsPlusPeerEbAddress(peerDevice, peerEbAddress);
+            return true;
+        }
+        return false;
+    }
+
+    boolean setTwsPlusAutoConnect(byte[] address, boolean autoConnect) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        BluetoothDevice device = mRemoteDevices.getDevice(address);
+        BluetoothDevice peerDevice = null;
+        DeviceProperties deviceProp;
+
+        if (device == null) {
+            deviceProp = mRemoteDevices.addDeviceProperties(address);
+        } else {
+            deviceProp = mRemoteDevices.getDeviceProperties(device);
+        }
+        if(deviceProp != null) {
+            deviceProp.setTwsPlusAutoConnect(peerDevice, autoConnect);
+            return true;
+        }
+        return false;
+    }
+
+    void updateHostFeatureSupport(byte[] val) {
+        mAdapterProperties.updateHostFeatureSupport(val);
+    }
+
+    void updateSocFeatureSupport(byte[] val) {
+        mAdapterProperties.updateSocFeatureSupport(val);
+    }
+
     int getScanMode() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
@@ -1721,6 +2001,10 @@
         debugLog("startDiscovery");
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
 
+        if (mAdapterProperties.isDiscovering()) {
+            Log.i(TAG,"discovery already active, ignore startDiscovery");
+            return false;
+        }
         return startDiscoveryNative();
     }
 
@@ -1728,6 +2012,10 @@
         debugLog("cancelDiscovery");
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
 
+        if (!mAdapterProperties.isDiscovering()) {
+            Log.i(TAG,"discovery not active, ignore cancelDiscovery");
+            return false;
+        }
         return cancelDiscoveryNative();
     }
 
@@ -1774,6 +2062,35 @@
         }
     }
 
+    public boolean isTwsPlusDevice(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
+        if (deviceProp == null) {
+            return false;
+        }
+        return (deviceProp.getTwsPlusPeerAddress() != null);
+    }
+
+    public String getTwsPlusPeerAddress(BluetoothDevice device)  {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
+        if (deviceProp == null) {
+            return null;
+        }
+        byte[] address = deviceProp.getTwsPlusPeerAddress();
+        return Utils.getAddressStringFromByte(address);
+    }
+
+    public BluetoothDevice getTwsPlusPeerDevice(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
+        if (deviceProp == null) {
+            return null;
+        }
+        byte[] address = deviceProp.getTwsPlusPeerAddress();
+        return mRemoteDevices.getDevice(address);
+    }
+
     boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
@@ -1785,7 +2102,11 @@
 
         // Pairing is unreliable while scanning, so cancel discovery
         // Note, remove this when native stack improves
-        cancelDiscoveryNative();
+        if (!mAdapterProperties.isDiscovering()) {
+            Log.i(TAG,"discovery not active, no need to send cancelDiscovery");
+        } else {
+            cancelDiscoveryNative();
+        }
 
         Message msg = mBondStateMachine.obtainMessage(BondStateMachine.CREATE_BOND);
         msg.obj = device;
@@ -1889,6 +2210,10 @@
      */
     public String getRemoteName(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (device.getAddress().equals(BATService.mBAAddress)) {
+            Log.d(TAG," Request Name for BA device ");
+            return "Broadcast_Audio";
+        }
         if (mRemoteDevices == null) {
             return null;
         }
@@ -1955,6 +2280,10 @@
 
     boolean fetchRemoteUuids(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (device.getAddress().equals(BATService.mBAAddress)) {
+            Log.d(TAG," Update from BA, don't check UUIDS, bail out");
+            return false;
+        }
         mRemoteDevices.fetchUuids(device);
         return true;
     }
@@ -2197,6 +2526,312 @@
         return mAdapterProperties.isA2dpOffloadEnabled();
     }
 
+    /**
+     * Check whether Wipower Fastboot enabled.
+     *
+     * @return true if Wipower fastboot is enabled
+     */
+    public boolean isWipowerFastbootEnabled() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isWipowerFastbootEnabled();
+    }
+
+    /**
+     * Check whether Split A2DP Scramble Data Required
+     *
+     * @return true if Split A2DP Scramble Data Required is enabled
+     */
+    public boolean isSplitA2DPScrambleDataRequired() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPScrambleDataRequired();
+    }
+
+    /**
+     * Check whether Split A2DP 44.1Khz Sample Freq enabled.
+     *
+     * @return true if Split A2DP 44.1Khz Sample Freq is enabled
+     */
+    public boolean isSplitA2DP44p1KhzSampleFreq() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DP44p1KhzSampleFreq();
+    }
+
+    /**
+     * Check whether Split A2DP 48Khz Sample Freq enabled.
+     *
+     * @return true if  Split A2DP 48Khz Sample Freq is enabled
+     */
+    public boolean isSplitA2DP48KhzSampleFreq() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DP48KhzSampleFreq();
+    }
+
+    /**
+     * Check whether Split A2DP Single VS Command Support enabled.
+     *
+     * @return true if Split A2DP Single VSCommand Support is enabled
+     */
+    public boolean isSplitA2DPSingleVSCommandSupport() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSingleVSCommandSupport();
+    }
+
+    /**
+     * Check whether Split A2DP Source SBC Encoding enabled.
+     *
+     * @return true if Split A2DP Source SBC Encoding is enabled
+     */
+    public boolean isSplitA2DPSourceSBCEncoding() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSourceSBCEncoding();
+    }
+
+    /**
+     * Check whether Split A2DP Source SBC  enabled.
+     *
+     * @return true if Split A2DP Source SBC  is enabled
+     */
+    public boolean isSplitA2DPSourceSBC() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSourceSBC();
+    }
+
+    /**
+     * Check whether Split A2DP Source MP3  enabled.
+     *
+     * @return true if Split A2DP Source MP3  is enabled
+     */
+    public boolean isSplitA2DPSourceMP3() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSourceMP3();
+    }
+
+    /**
+     * Check whether Split A2DP Source AAC  enabled.
+     *
+     * @return true if Split A2DP Source AAC  is enabled
+     */
+    public boolean isSplitA2DPSourceAAC() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSourceAAC();
+    }
+
+    /**
+     * Check whether Split A2DP Source LDAC  enabled.
+     *
+     * @return true if Split A2DP Source LDAC  is enabled
+     */
+    public boolean isSplitA2DPSourceLDAC() {
+        String BT_SOC = SystemProperties.get("vendor.bluetooth.soc");
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return (!mAdapterProperties.isAddonFeaturesCmdSupported() && BT_SOC.equals("cherokee")) ||
+            mAdapterProperties.isSplitA2DPSourceLDAC();
+    }
+
+    /**
+     * Check whether Split A2DP Source APTX  enabled.
+     *
+     * @return true if Split A2DP Source APTX  is enabled
+     */
+    public boolean isSplitA2DPSourceAPTX() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSourceAPTX();
+    }
+
+    /**
+     * Check whether Split A2DP Source APTX HD  enabled.
+     *
+     * @return true if Split A2DP Source APTX HD  is enabled
+     */
+    public boolean isSplitA2DPSourceAPTXHD() {
+        String BT_SOC = SystemProperties.get("vendor.bluetooth.soc");
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return (!mAdapterProperties.isAddonFeaturesCmdSupported() && BT_SOC.equals("cherokee")) ||
+            mAdapterProperties.isSplitA2DPSourceAPTXHD();
+    }
+
+    /**
+     * Check whether Split A2DP Source APTX ADAPTIVE  enabled.
+     *
+     * @return true if Split A2DP Source APTX ADAPTIVE  is enabled
+     */
+    public boolean isSplitA2DPSourceAPTXADAPTIVE() {
+        String BT_SOC = SystemProperties.get("vendor.bluetooth.soc");
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return (!mAdapterProperties.isAddonFeaturesCmdSupported() && BT_SOC.equals("cherokee")) ||
+            mAdapterProperties.isSplitA2DPSourceAPTXADAPTIVE();
+    }
+
+    /**
+     * Check whether Split A2DP Source APTX TWS+  enabled.
+     *
+     * @return true if Split A2DP Source APTX TWS+  is enabled
+     */
+    public boolean isSplitA2DPSourceAPTXTWSPLUS() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSourceAPTXTWSPLUS();
+    }
+
+    /**
+     * Check whether Split A2DP Sink SBC  enabled.
+     *
+     * @return true if Split A2DP Sink SBC  is enabled
+     */
+    public boolean isSplitA2DPSinkSBC() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSinkSBC();
+    }
+
+    /**
+     * Check whether Split A2DP Sink MP3  enabled.
+     *
+     * @return true if Split A2DP Sink MP3  is enabled
+     */
+    public boolean isSplitA2DPSinkMP3() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSinkMP3();
+    }
+
+    /**
+     * Check whether Split A2DP Sink AAC  enabled.
+     *
+     * @return true if Split A2DP Sink AAC  is enabled
+     */
+    public boolean isSplitA2DPSinkAAC() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSinkAAC();
+    }
+
+    /**
+     * Check whether Split A2DP Sink LDAC  enabled.
+     *
+     * @return true if Split A2DP Sink LDAC  is enabled
+     */
+    public boolean isSplitA2DPSinkLDAC() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSinkLDAC();
+    }
+
+    /**
+     * Check whether Split A2DP Sink APTX  enabled.
+     *
+     * @return true if Split A2DP Sink APTX  is enabled
+     */
+    public boolean isSplitA2DPSinkAPTX() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSinkAPTX();
+    }
+
+    /**
+     * Check whether Split A2DP Sink APTX HD  enabled.
+     *
+     * @return true if Split A2DP Sink APTX HD  is enabled
+     */
+    public boolean isSplitA2DPSinkAPTXHD() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSinkAPTXHD();
+    }
+
+    /**
+     * Check whether Split A2DP Sink APTX ADAPTIVE  enabled.
+     *
+     * @return true if Split A2DP Sink APTX ADAPTIVE  is enabled
+     */
+    public boolean isSplitA2DPSinkAPTXADAPTIVE() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSinkAPTXADAPTIVE();
+    }
+
+    /**
+     * Check whether Split A2DP Sink APTX TWS+  enabled.
+     *
+     * @return true if Split A2DP Sink APTX TWS+  is enabled
+     */
+    public boolean isSplitA2DPSinkAPTXTWSPLUS() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSplitA2DPSinkAPTXTWSPLUS();
+    }
+
+    /**
+     * Check whether Voice Dual SCO  enabled.
+     *
+     * @return true if Voice Dual SCO is enabled
+     */
+    public boolean isVoiceDualSCO() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isVoiceDualSCO();
+    }
+
+    /**
+     * Check whether Voice TWS+ eSCO AG enabled.
+     *
+     * @return true if Voice TWS+ eSCO AG  is enabled
+     */
+    public boolean isVoiceTWSPLUSeSCOAG() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isVoiceTWSPLUSeSCOAG();
+    }
+
+    /**
+     * Check whether SWB Voice with Aptx Adaptive AG  enabled.
+     *
+     * @return true if SWB Voice with Aptx Adaptive AG  is enabled
+     */
+    public boolean isSWBVoicewithAptxAdaptiveAG() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isSWBVoicewithAptxAdaptiveAG();
+    }
+
+    /**
+     * Check whether Broadcast Audio Tx with EC-2:5 enabled.
+     *
+     * @return true if Broadcast Audio Tx with EC-2:5 is enabled
+     */
+    public boolean isBroadcastAudioTxwithEC_2_5() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isBroadcastAudioTxwithEC_2_5();
+    }
+
+    /**
+     * Check whether Broadcast Audio Tx with EC_3:9 enabled.
+     *
+     * @return true if Broadcast Audio Tx with EC_3:9 is enabled
+     */
+    public boolean isBroadcastAudioTxwithEC_3_9() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isBroadcastAudioTxwithEC_3_9();
+    }
+
+    /**
+     * Check whether Broadcast Audio Rx with EC_2:5 enabled.
+     *
+     * @return true if Broadcast Audio Rx with EC_2:5 is enabled
+     */
+    public boolean isBroadcastAudioRxwithEC_2_5() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isBroadcastAudioRxwithEC_2_5();
+    }
+
+    /**
+     * Check whether Broadcast Audio Rx with EC_3:9 enabled.
+     *
+     * @return true if Broadcast Audio Rx with EC_3:9 is enabled
+     */
+    public boolean isBroadcastAudioRxwithEC_3_9() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isBroadcastAudioRxwithEC_3_9();
+    }
+
+    /**
+     * Check  AddonFeatures Cmd Support.
+     *
+     * @return true if AddonFeatures Cmd is Supported
+     */
+    public boolean isAddonFeaturesCmdSupported() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isAddonFeaturesCmdSupported();
+    }
+
     private BluetoothActivityEnergyInfo reportActivityInfo() {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH permission");
         if (mAdapterProperties.getState() != BluetoothAdapter.STATE_ON
@@ -2250,6 +2885,12 @@
         return mAdapterProperties.getTotalNumOfTrackableAdvertisements();
     }
 
+    void updateQuietModeStatus(boolean quietMode) {
+        debugLog("updateQuietModeStatus()-updateQuietModeStatus called with quiet mode status:"
+                   + quietMode);
+        mQuietmode = quietMode;
+    }
+
     void onLeServiceUp() {
         mAdapterStateMachine.sendMessage(AdapterState.USER_TURN_ON);
     }
@@ -2258,6 +2899,19 @@
         mAdapterStateMachine.sendMessage(AdapterState.BLE_TURN_OFF);
     }
 
+    int setSocketOpt(int type, int channel, int optionName, byte [] optionVal,
+             int optionLen) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        return mVendorSocket.setSocketOpt(type, channel, optionName, optionVal, optionLen);
+    }
+
+    int getSocketOpt(int type, int channel, int optionName, byte [] optionVal) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        return mVendorSocket.getSocketOpt(type, channel, optionName, optionVal);
+    }
+
     private static int convertScanModeToHal(int mode) {
         switch (mode) {
             case BluetoothAdapter.SCAN_MODE_NONE:
@@ -2508,6 +3162,24 @@
         }
     };
 
+    private final BroadcastReceiver mWifiStateBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) && isEnabled()) {
+                NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+                NetworkInfo.DetailedState ds = networkInfo.getDetailedState();
+                if (ds == NetworkInfo.DetailedState.CONNECTED) {
+                    if(isVendorIntfEnabled())
+                        mVendor.setWifiState(true);
+                }
+                else if (ds == NetworkInfo.DetailedState.DISCONNECTED) {
+                    if(isVendorIntfEnabled())
+                        mVendor.setWifiState(false);
+                }
+            }
+        }
+    };
+
     private void enableNativeWithGuestFlag() {
         boolean isGuest = UserManager.get(this).isGuestUser();
         if (!enableNative(isGuest)) {
diff --git a/src/com/android/bluetooth/btservice/AdapterState.java b/src/com/android/bluetooth/btservice/AdapterState.java
index 94feef2..46d8297 100644
--- a/src/com/android/bluetooth/btservice/AdapterState.java
+++ b/src/com/android/bluetooth/btservice/AdapterState.java
@@ -69,11 +69,21 @@
     static final int BREDR_STOP_TIMEOUT = 10;
     static final int BLE_STOP_TIMEOUT = 11;
     static final int BLE_START_TIMEOUT = 12;
+    static final int BEGIN_BREDR_STOP = 13;
+    static final int STACK_DISABLED = 14;
+    static final int STACK_DISABLE_TIMEOUT = 15;
+    static final int BREDR_CLEANUP_TIMEOUT = 16;
+    static final int BT_FORCEKILL_TIMEOUT = 17;
 
-    static final int BLE_START_TIMEOUT_DELAY = 4000;
+    // TODO: To be optimized : Increased BLE_START_TIMEOUT_DELAY to 6 sec
+    // as OMR1 Total timeout value was 14 seconds
+    static final int BLE_START_TIMEOUT_DELAY = 6000;
     static final int BLE_STOP_TIMEOUT_DELAY = 1000;
     static final int BREDR_START_TIMEOUT_DELAY = 4000;
     static final int BREDR_STOP_TIMEOUT_DELAY = 4000;
+    static final int BREDR_CLEANUP_TIMEOUT_DELAY = 2000;
+    static final int STACK_DISABLE_TIMEOUT_DELAY = 8000;
+    static final int BT_FORCEKILL_TIMEOUT_DELAY = 100;
 
     private AdapterService mAdapterService;
     private TurningOnState mTurningOnState = new TurningOnState();
@@ -181,6 +191,11 @@
                     transitionTo(mTurningBleOnState);
                     break;
 
+                case BT_FORCEKILL_TIMEOUT:
+                    errorLog("Killing the process to force a restart as part of cleanup");
+                    android.os.Process.killProcess(android.os.Process.myPid());
+                    break;
+
                 default:
                     infoLog("Unhandled message - " + messageString(msg.what));
                     return false;
@@ -207,6 +222,12 @@
                     transitionTo(mTurningBleOffState);
                     break;
 
+                case BT_FORCEKILL_TIMEOUT:
+                    transitionTo(mOffState);
+                    errorLog("Killing the process to force a restart as part of cleanup");
+                    android.os.Process.killProcess(android.os.Process.myPid());
+                    break;
+
                 default:
                     infoLog("Unhandled message - " + messageString(msg.what));
                     return false;
@@ -229,6 +250,12 @@
                     transitionTo(mTurningOffState);
                     break;
 
+                case BT_FORCEKILL_TIMEOUT:
+                    transitionTo(mOffState);
+                    errorLog("Killing the process to force a restart as part of cleanup");
+                    android.os.Process.killProcess(android.os.Process.myPid());
+                    break;
+
                 default:
                     infoLog("Unhandled message - " + messageString(msg.what));
                     return false;
@@ -266,7 +293,16 @@
 
                 case BLE_START_TIMEOUT:
                     errorLog(messageString(msg.what));
-                    transitionTo(mTurningBleOffState);
+                    mAdapterService.disableProfileServices(true);
+                    mAdapterService.StartHCIClose();
+                    errorLog("BLE_START_TIMEOUT is going to kill the process as part of cleanup");
+                    sendMessageDelayed(BT_FORCEKILL_TIMEOUT, BT_FORCEKILL_TIMEOUT_DELAY);
+                    break;
+
+                case BT_FORCEKILL_TIMEOUT:
+                    transitionTo(mOffState);
+                    errorLog("Killing the process to force a restart as part of cleanup");
+                    android.os.Process.killProcess(android.os.Process.myPid());
                     break;
 
                 default:
@@ -306,7 +342,16 @@
 
                 case BREDR_START_TIMEOUT:
                     errorLog(messageString(msg.what));
-                    transitionTo(mTurningOffState);
+                    mAdapterService.disableProfileServices(false);
+                    mAdapterService.StartHCIClose();
+                    errorLog("BREDR_START_TIMEOUT is going to kill the process as part of cleanup");
+                    sendMessageDelayed(BT_FORCEKILL_TIMEOUT, BT_FORCEKILL_TIMEOUT_DELAY);
+                    break;
+
+                case BT_FORCEKILL_TIMEOUT:
+                    transitionTo(mOffState);
+                    errorLog("Killing the process to force a restart as part of cleanup");
+                    android.os.Process.killProcess(android.os.Process.myPid());
                     break;
 
                 default:
@@ -327,8 +372,9 @@
         @Override
         public void enter() {
             super.enter();
-            sendMessageDelayed(BREDR_STOP_TIMEOUT, BREDR_STOP_TIMEOUT_DELAY);
-            mAdapterService.stopProfileServices();
+            Log.w(TAG,"Calling startBrEdrCleanup");
+            sendMessageDelayed(BREDR_CLEANUP_TIMEOUT, BREDR_CLEANUP_TIMEOUT_DELAY);
+            mAdapterService.startBrEdrCleanup();
         }
 
         @Override
@@ -346,7 +392,30 @@
 
                 case BREDR_STOP_TIMEOUT:
                     errorLog(messageString(msg.what));
-                    transitionTo(mTurningBleOffState);
+                    mAdapterService.disableProfileServices(false);
+                    mAdapterService.StartHCIClose();
+                    errorLog("BREDR_STOP_TIMEOUT is going to kill the process as part of cleanup");
+                    sendMessageDelayed(BT_FORCEKILL_TIMEOUT, BT_FORCEKILL_TIMEOUT_DELAY);
+                    break;
+
+                case BT_FORCEKILL_TIMEOUT:
+                    transitionTo(mOffState);
+                    errorLog("Killing the process to force a restart as part of cleanup");
+                    android.os.Process.killProcess(android.os.Process.myPid());
+                    break;
+
+                case BREDR_CLEANUP_TIMEOUT:
+                    errorLog("Error cleaningup Bluetooth profiles (cleanup timeout)");
+                    mAdapterService.disableProfileServices(false);
+                    mAdapterService.StartHCIClose();
+                    errorLog("BREDR_CLEANUP_TIMEOUT going to kill the process as part of cleanup");
+                    sendMessageDelayed(BT_FORCEKILL_TIMEOUT, BT_FORCEKILL_TIMEOUT_DELAY);
+                    break;
+
+                case BEGIN_BREDR_STOP:
+                    removeMessages(BREDR_CLEANUP_TIMEOUT);
+                    sendMessageDelayed(BREDR_STOP_TIMEOUT, BREDR_STOP_TIMEOUT_DELAY);
+                    mAdapterService.stopProfileServices();
                     break;
 
                 default:
@@ -367,8 +436,13 @@
         @Override
         public void enter() {
             super.enter();
-            sendMessageDelayed(BLE_STOP_TIMEOUT, BLE_STOP_TIMEOUT_DELAY);
-            mAdapterService.bringDownBle();
+            sendMessageDelayed(STACK_DISABLE_TIMEOUT, STACK_DISABLE_TIMEOUT_DELAY);
+            boolean ret = mAdapterService.disableNative();
+            if (!ret) {
+               removeMessages(STACK_DISABLE_TIMEOUT);
+               errorLog("Error while calling disableNative");
+               transitionTo(mBleOnState);
+            }
         }
 
         @Override
@@ -385,10 +459,30 @@
                     break;
 
                 case BLE_STOP_TIMEOUT:
+                    errorLog("Error stopping Bluetooth profiles (BLE stop timeout)");
                     errorLog(messageString(msg.what));
                     transitionTo(mOffState);
                     break;
 
+                case STACK_DISABLED:
+                    removeMessages(STACK_DISABLE_TIMEOUT);
+                    sendMessageDelayed(BLE_STOP_TIMEOUT, BLE_STOP_TIMEOUT_DELAY);
+                    mAdapterService.bringDownBle();
+                    break;
+
+                case STACK_DISABLE_TIMEOUT:
+                    mAdapterService.disableProfileServices(true);
+                    mAdapterService.StartHCIClose();
+                    errorLog("STACK_DISABLE_TIMEOUT going to kill the process as part of cleanup");
+                    sendMessageDelayed(BT_FORCEKILL_TIMEOUT, BT_FORCEKILL_TIMEOUT_DELAY);
+                    break;
+
+                case BT_FORCEKILL_TIMEOUT:
+                    transitionTo(mOffState);
+                    errorLog("Killing the process to force a restart as part of cleanup");
+                    android.os.Process.killProcess(android.os.Process.myPid());
+                    break;
+
                 default:
                     infoLog("Unhandled message - " + messageString(msg.what));
                     return false;
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index 13ef2ad..6aa0c55 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -1,4 +1,6 @@
 /*
+ * Copyright (C) 2018 The Linux Foundation. All rights reserved.
+ * Not a Contribution
  * Copyright (C) 2012 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,6 +27,7 @@
 import android.os.Message;
 import android.os.UserHandle;
 import android.util.Log;
+import android.os.PowerManager;
 
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.a2dp.A2dpService;
@@ -66,13 +69,20 @@
     private RemoteDevices mRemoteDevices;
     private BluetoothAdapter mAdapter;
 
+    /* The WakeLock is used for bringing up the LCD during a pairing request
+     * from remote device when Android is in Suspend state.*/
+    private PowerManager.WakeLock mWakeLock;
+
     private PendingCommandState mPendingCommandState = new PendingCommandState();
     private StableState mStableState = new StableState();
 
     public static final String OOBDATA = "oobdata";
 
-    private BondStateMachine(AdapterService service, AdapterProperties prop,
-            RemoteDevices remoteDevices) {
+    private final ArrayList<BluetoothDevice> mDevices =
+        new ArrayList<BluetoothDevice>();
+
+    private BondStateMachine(PowerManager pm, AdapterService service,
+            AdapterProperties prop, RemoteDevices remoteDevices) {
         super("BondStateMachine:");
         addState(mStableState);
         addState(mPendingCommandState);
@@ -81,17 +91,22 @@
         mAdapterProperties = prop;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         setInitialState(mStableState);
+
+        //WakeLock instantiation in RemoteDevices class
+        mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP
+                       | PowerManager.ON_AFTER_RELEASE, TAG);
+        mWakeLock.setReferenceCounted(false);
     }
 
-    public static BondStateMachine make(AdapterService service, AdapterProperties prop,
-            RemoteDevices remoteDevices) {
+    public static BondStateMachine make(PowerManager pm, AdapterService service,
+            AdapterProperties prop, RemoteDevices remoteDevices) {
         Log.d(TAG, "make");
-        BondStateMachine bsm = new BondStateMachine(service, prop, remoteDevices);
+        BondStateMachine bsm = new BondStateMachine(pm, service, prop, remoteDevices);
         bsm.start();
         return bsm;
     }
 
-    public void doQuit() {
+    public synchronized void doQuit() {
         quitNow();
     }
 
@@ -113,7 +128,7 @@
         }
 
         @Override
-        public boolean processMessage(Message msg) {
+        public synchronized boolean processMessage(Message msg) {
 
             BluetoothDevice dev = (BluetoothDevice) msg.obj;
 
@@ -132,12 +147,15 @@
                     break;
                 case BONDING_STATE_CHANGE:
                     int newState = msg.arg1;
-                /* if incoming pairing, transition to pending state */
+                    /* if incoming pairing, transition to pending state */
                     if (newState == BluetoothDevice.BOND_BONDING) {
+                        if (!mDevices.contains(dev)) {
+                            mDevices.add(dev);
+                        }
                         sendIntent(dev, newState, 0);
                         transitionTo(mPendingCommandState);
                     } else if (newState == BluetoothDevice.BOND_NONE) {
-                    /* if the link key was deleted by the stack */
+                        /* if the link key was deleted by the stack */
                         sendIntent(dev, newState, 0);
                     } else {
                         Log.e(TAG, "In stable state, received invalid newState: "
@@ -156,7 +174,6 @@
 
 
     private class PendingCommandState extends State {
-        private final ArrayList<BluetoothDevice> mDevices = new ArrayList<BluetoothDevice>();
 
         @Override
         public void enter() {
@@ -165,7 +182,7 @@
         }
 
         @Override
-        public boolean processMessage(Message msg) {
+        public synchronized boolean processMessage(Message msg) {
             BluetoothDevice dev = (BluetoothDevice) msg.obj;
             DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
             boolean result = false;
@@ -196,6 +213,14 @@
                     int reason = getUnbondReasonFromHALCode(msg.arg2);
                     sendIntent(dev, newState, reason);
                     if (newState != BluetoothDevice.BOND_BONDING) {
+                        // check if bond none is received from device which
+                        // was in pairing state otherwise don't transition to
+                        // stable state.
+                        if (newState == BluetoothDevice.BOND_NONE &&
+                            !mDevices.contains(dev) && mDevices.size() != 0) {
+                            infoLog("not transitioning to stable state");
+                            break;
+                        }
                         /* this is either none/bonded, remove and transition */
                         result = !mDevices.remove(dev);
                         if (mDevices.isEmpty()) {
@@ -223,11 +248,21 @@
                 case SSP_REQUEST:
                     int passkey = msg.arg1;
                     int variant = msg.arg2;
+                    if (devProp == null)
+                    {
+                        Log.e(TAG,"Received msg from an unknown device");
+                        return false;
+                    }
                     sendDisplayPinIntent(devProp.getAddress(), passkey, variant);
                     break;
                 case PIN_REQUEST:
                     BluetoothClass btClass = dev.getBluetoothClass();
                     int btDeviceClass = btClass.getDeviceClass();
+                    if (devProp == null)
+                    {
+                        Log.e(TAG,"Received msg from an unknown device");
+                        return false;
+                    }
                     if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD || btDeviceClass
                             == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) {
                         // Its a keyboard. Follow the HID spec recommendation of creating the
@@ -267,6 +302,7 @@
     }
 
     private boolean cancelBond(BluetoothDevice dev) {
+        if (mAdapterService == null) return false;
         if (dev.getBondState() == BluetoothDevice.BOND_BONDING) {
             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
             if (!mAdapterService.cancelBondNative(addr)) {
@@ -279,6 +315,7 @@
     }
 
     private boolean removeBond(BluetoothDevice dev, boolean transition) {
+        if (mAdapterService == null) return false;
         if (dev.getBondState() == BluetoothDevice.BOND_BONDED) {
             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
             if (!mAdapterService.removeBondNative(addr)) {
@@ -286,6 +323,7 @@
             } else {
                 if (transition) {
                     transitionTo(mPendingCommandState);
+                    dev.setAlias(null);
                 }
                 return true;
             }
@@ -296,6 +334,7 @@
 
     private boolean createBond(BluetoothDevice dev, int transport, OobData oobData,
             boolean transition) {
+        if (mAdapterService == null) return false;
         if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
             infoLog("Bond address is:" + dev);
             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
@@ -318,6 +357,9 @@
     }
 
     private void sendDisplayPinIntent(byte[] address, int pin, int variant) {
+
+        // Acquire wakelock during PIN code request to bring up LCD display
+        mWakeLock.acquire();
         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevices.getDevice(address));
         if (pin != 0) {
@@ -328,6 +370,8 @@
         // Workaround for Android Auto until pre-accepting pairing requests is added.
         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
+        // Release wakelock to allow the LCD to go off after the PIN popup notification.
+        mWakeLock.release();
     }
 
     private void sendIntent(BluetoothDevice device, int newState, int reason) {
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index 8a9c0a1..7b6f37b 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -23,6 +23,7 @@
 import android.provider.Settings;
 import android.util.FeatureFlagUtils;
 import android.util.Log;
+import android.os.SystemProperties;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.a2dp.A2dpService;
@@ -43,6 +44,7 @@
 import com.android.bluetooth.pbap.BluetoothPbapService;
 import com.android.bluetooth.pbapclient.PbapClientService;
 import com.android.bluetooth.sap.SapService;
+import com.android.bluetooth.ba.BATService;
 
 import java.util.ArrayList;
 
@@ -101,7 +103,9 @@
             new ProfileConfig(BluetoothPbapService.class, R.bool.profile_supported_pbap,
                     (1 << BluetoothProfile.PBAP)),
             new ProfileConfig(HearingAidService.class, R.bool.profile_supported_hearing_aid,
-                    (1 << BluetoothProfile.HEARING_AID))
+                    (1 << BluetoothProfile.HEARING_AID)),
+            new ProfileConfig(BATService.class, R.bool.profile_supported_ba,
+                    (1 << BluetoothProfile.BA_TRANSMITTER))
     };
 
     private static Class[] sSupportedProfiles = new Class[0];
@@ -126,6 +130,10 @@
             }
 
             if (supported && !isProfileDisabled(ctx, config.mMask)) {
+                if (!addAudioProfiles(config.mClass.getSimpleName())) {
+                    Log.i(TAG, " Profile " + config.mClass.getSimpleName() + " Not added ");
+                    continue;
+                }
                 Log.v(TAG, "Adding " + config.mClass.getSimpleName());
                 profiles.add(config.mClass);
             }
@@ -162,4 +170,38 @@
 
         return (disabledProfilesBitMask & profileMask) != 0;
     }
+
+    private static synchronized boolean addAudioProfiles(String serviceName) {
+        Log.d(TAG," addAudioProfiles profile" + serviceName);
+        boolean isA2dpConcurrency= SystemProperties.getBoolean(
+                  "persist.vendor.service.bt.a2dp_concurrency", false);
+        Log.i(TAG, "addAudioProfiles isA2dpConcurrency:" + isA2dpConcurrency);
+
+        if(isA2dpConcurrency) {
+            if ((serviceName.equals("A2dpSinkService")) || (serviceName.equals("A2dpService"))) {
+                return true;
+            }
+        } else {
+            boolean isA2dpSink = SystemProperties.getBoolean(
+                    "persist.vendor.service.bt.a2dp.sink", false);
+            Log.i(TAG, "addAudioProfiles isA2dpSink :" + isA2dpSink);
+            /* If property not enabled and request is for A2DPSinkService, don't add */
+            if ((serviceName.equals("A2dpSinkService")) && (!isA2dpSink))
+                return false;
+            if ((serviceName.equals("A2dpService")) && (isA2dpSink))
+                return false;
+        }
+
+        boolean isBAEnabled = SystemProperties.getBoolean("persist.vendor.service.bt.bca", false);
+        boolean isSplitA2dpSupported = SystemProperties.
+            getBoolean("persist.vendor.btstack.enable.splita2dp", true);
+
+        if(serviceName.equals("BATService")) {
+            Log.d(TAG," isBAEnabled = " + isBAEnabled
+                          + " isSplitEnabled " + isSplitA2dpSupported);
+            return isBAEnabled && isSplitA2dpSupported;
+        }
+        // always return true for other profiles
+        return true;
+    }
 }
diff --git a/src/com/android/bluetooth/btservice/PhonePolicy.java b/src/com/android/bluetooth/btservice/PhonePolicy.java
index a0ad490..7cd45f9 100644
--- a/src/com/android/bluetooth/btservice/PhonePolicy.java
+++ b/src/com/android/bluetooth/btservice/PhonePolicy.java
@@ -1,3 +1,4 @@
+
 /*
  * Copyright (C) 2017 The Android Open Source Project
  *
@@ -17,6 +18,7 @@
 package com.android.bluetooth.btservice;
 
 import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
@@ -35,10 +37,12 @@
 import android.util.Log;
 
 import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
 import com.android.bluetooth.hearingaid.HearingAidService;
 import com.android.bluetooth.hfp.HeadsetService;
 import com.android.bluetooth.hid.HidHostService;
 import com.android.bluetooth.pan.PanService;
+import com.android.bluetooth.ba.BATService;
 import com.android.internal.R;
 
 import java.util.HashSet;
@@ -78,10 +82,17 @@
     private static final int MESSAGE_PROFILE_INIT_PRIORITIES = 2;
     private static final int MESSAGE_CONNECT_OTHER_PROFILES = 3;
     private static final int MESSAGE_ADAPTER_STATE_TURNED_ON = 4;
-    private static final int MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED = 5;
+    private static final int MESSAGE_AUTO_CONNECT_PROFILES = 50;
 
     // Timeouts
+    private static final int AUTO_CONNECT_PROFILES_TIMEOUT= 500;
     @VisibleForTesting static int sConnectOtherProfilesTimeoutMillis = 6000; // 6s
+    private static final int CONNECT_OTHER_PROFILES_TIMEOUT_DELAYED = 10000; //10s
+    private static final int CONNECT_OTHER_PROFILES_REDUCED_TIMEOUT_DELAYED = 2000; //2s
+
+    private static final int MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED = 5;
+    private static final String delayConnectTimeoutDevice[] = {"00:23:3D"}; // volkswagen carkit
+    private static final String delayReducedConnectTimeoutDevice[] = {"10:4F:A8"}; //h.ear (MDR-EX750BT)
 
     private final AdapterService mAdapterService;
     private final ServiceFactory mFactory;
@@ -110,11 +121,21 @@
                             BluetoothProfile.A2DP, -1, // No-op argument
                             intent).sendToTarget();
                     break;
+                case BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED:
+                    mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED,
+                            BluetoothProfile.A2DP_SINK,-1, // No-op argument
+                            intent).sendToTarget();
+                    break;
                 case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
                     mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
                             BluetoothProfile.A2DP, -1, // No-op argument
                             intent).sendToTarget();
                     break;
+                case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
+                    mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
+                            BluetoothProfile.HEADSET, -1, // No-op argument
+                            intent).sendToTarget();
+                    break;
                 case BluetoothAdapter.ACTION_STATE_CHANGED:
                     // Only pass the message on if the adapter has actually changed state from
                     // non-ON to ON. NOTE: ON is the state depicting BREDR ON and not just BLE ON.
@@ -168,6 +189,10 @@
                     Intent intent = (Intent) msg.obj;
                     BluetoothDevice device =
                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    if (device.getAddress().equals(BATService.mBAAddress)) {
+                        Log.d(TAG," Update from BA, bail out");
+                        break;
+                    }
                     int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
                     int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
                     processProfileStateChanged(device, msg.arg1, nextState, prevState);
@@ -195,6 +220,11 @@
                     resetStates();
                     autoConnect();
                     break;
+                case MESSAGE_AUTO_CONNECT_PROFILES: {
+                    if (DBG) debugLog( "MESSAGE_AUTO_CONNECT_PROFILES");
+                    autoConnectProfilesDelayed();
+                    break;
+                }
             }
         }
     }
@@ -206,9 +236,11 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothDevice.ACTION_UUID);
         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
         filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+        filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
         mAdapterService.registerReceiver(mReceiver, filter);
     }
 
@@ -228,9 +260,14 @@
         debugLog("processInitProfilePriorities() - device " + device);
         HidHostService hidService = mFactory.getHidHostService();
         A2dpService a2dpService = mFactory.getA2dpService();
+        A2dpSinkService a2dpSinkService = mFactory.getA2dpSinkService();
         HeadsetService headsetService = mFactory.getHeadsetService();
         PanService panService = mFactory.getPanService();
         HearingAidService hearingAidService = mFactory.getHearingAidService();
+        BluetoothDevice peerTwsDevice = null;
+        if (mAdapterService.isTwsPlusDevice(device)) {
+            peerTwsDevice = mAdapterService.getTwsPlusPeerDevice(device);
+        }
 
         // 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
@@ -245,12 +282,27 @@
                 || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) && (
                 headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED))) {
             headsetService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            if (peerTwsDevice != null) {
+                debugLog("setting peer earbud to priority on for hfp" + peerTwsDevice);
+                headsetService.setPriority(peerTwsDevice, 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 (peerTwsDevice != null) {
+                debugLog("setting peer earbud to priority on for a2dp" + peerTwsDevice);
+                a2dpService.setPriority(peerTwsDevice, BluetoothProfile.PRIORITY_ON);
+            }
+        }
+
+        if ((a2dpSinkService != null)
+                && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)
+                || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AdvAudioDist)) && (
+                a2dpSinkService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) {
+            a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_ON);
         }
 
         if ((panService != null) && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU) && (
@@ -272,14 +324,25 @@
             int prevState) {
         debugLog("processProfileStateChanged, device=" + device + ", profile=" + profileId + ", "
                 + prevState + " -> " + nextState);
-        if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET))) {
+        if ((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET)
+                || profileId == BluetoothProfile.A2DP_SINK) {
             if (nextState == BluetoothProfile.STATE_CONNECTED) {
+                debugLog("processProfileStateChanged: isTwsDevice: " + mAdapterService.isTwsPlusDevice(device));
                 switch (profileId) {
                     case BluetoothProfile.A2DP:
                         mA2dpRetrySet.remove(device);
+                        if (mAdapterService.isTwsPlusDevice(device)) {
+                             setAutoConnectForA2dpSink(device);
+                        }
                         break;
                     case BluetoothProfile.HEADSET:
                         mHeadsetRetrySet.remove(device);
+                        if (mAdapterService.isTwsPlusDevice(device)) {
+                             setAutoConnectForHeadset(device);
+                        }
+                        break;
+                    case BluetoothProfile.A2DP_SINK:
+                        setAutoConnectForA2dpSource(device);
                         break;
                 }
                 connectOtherProfile(device);
@@ -296,6 +359,9 @@
                 debugLog("processProfileStateChanged, device=" + device + ", a2dpDisconnected="
                         + a2dpDisconnected + ", hsDisconnected=" + hsDisconnected);
                 if (hsDisconnected && a2dpDisconnected) {
+                    //remove a2dp and headset retry set.
+                    mA2dpRetrySet.remove(device);
+                    mHeadsetRetrySet.remove(device);
                     removeAutoConnectFromA2dpSink(device);
                     removeAutoConnectFromHeadset(device);
                 }
@@ -304,6 +370,8 @@
     }
 
     private void processProfileActiveDeviceChanged(BluetoothDevice activeDevice, int profileId) {
+        HeadsetService hsService = mFactory.getHeadsetService();
+        A2dpService a2dpService = mFactory.getA2dpService();
         debugLog("processProfileActiveDeviceChanged, activeDevice=" + activeDevice + ", profile="
                 + profileId);
         switch (profileId) {
@@ -322,6 +390,43 @@
                 }
                 setAutoConnectForA2dpSink(activeDevice);
                 setAutoConnectForHeadset(activeDevice);
+                if ((mAdapterService != null) &&
+                    (mAdapterService.isTwsPlusDevice(activeDevice))) {
+                    BluetoothDevice peerTwsDevice =
+                        mAdapterService.getTwsPlusPeerDevice(activeDevice);
+                    if (peerTwsDevice != null) {
+                        if (a2dpService != null &&
+                            a2dpService.getConnectionState(peerTwsDevice) !=
+                            BluetoothProfile.STATE_DISCONNECTED) {
+                            debugLog("A2DP: Set Autoconnect for Peer TWS+ as well");
+                            setAutoConnectForA2dpSink(peerTwsDevice);
+                        }
+                        if (hsService != null &&
+                            hsService.getConnectionState(peerTwsDevice) !=
+                            BluetoothProfile.STATE_DISCONNECTED) {
+                            debugLog("HFP: Set Autoconnect for Peer TWS+ as well");
+                            setAutoConnectForHeadset(peerTwsDevice);
+                        }
+                    }
+                }
+                break;
+            case BluetoothProfile.HEADSET:
+                // Ignore null active device since we don't know if the change is triggered by
+                // normal device disconnection during Bluetooth shutdown or user action
+                if (activeDevice == null) {
+                    warnLog("processProfileActiveDeviceChanged: ignore null HFP active device");
+                    return;
+                }
+                // If a device with only HFP profile is connected then set autoconnection for
+                // that device.
+                if (a2dpService != null) {
+                     if (a2dpService.getConnectedDevices().size() == 0) {
+                         warnLog("processProfileActiveDeviceChanged: HFP active device changed and"+
+                         " no A2DP device connected, so setting priority to auto connect for HFP"+
+                         " device: " + activeDevice);
+                         setAutoConnectForHeadset(activeDevice);
+                     }
+                }
                 break;
         }
     }
@@ -331,7 +436,18 @@
         mA2dpRetrySet.clear();
     }
 
-    private void autoConnect() {
+    // Delaying Auto Connect to make sure that all clients
+    // are up and running, specially BluetoothHeadset.
+    public void autoConnect() {
+        debugLog( "delay auto connect by 500 ms");
+        if ((mHandler.hasMessages(MESSAGE_AUTO_CONNECT_PROFILES) == false) &&
+            (mAdapterService.isQuietModeEnabled()== false)) {
+            Message m = mHandler.obtainMessage(MESSAGE_AUTO_CONNECT_PROFILES);
+            mHandler.sendMessageDelayed(m,AUTO_CONNECT_PROFILES_TIMEOUT);
+        }
+    }
+
+    private void autoConnectProfilesDelayed() {
         if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) {
             errorLog("autoConnect: BT is not ON. Exiting autoConnect");
             return;
@@ -339,6 +455,8 @@
 
         if (!mAdapterService.isQuietModeEnabled()) {
             debugLog("autoConnect: Initiate auto connection on BT on...");
+            //Remote Device Profiles
+            autoConnectA2dpSink();
             final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
             if (bondedDevices == null) {
                 errorLog("autoConnect: bondedDevices are null");
@@ -385,6 +503,51 @@
         }
     }
 
+    private void autoConnectA2dpSink() {
+        A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
+        if (a2dpSinkService == null) {
+            errorLog("autoConnectA2dpSink, service is null");
+            return;
+        }
+        BluetoothDevice bondedDevices[] =  mAdapterService.getBondedDevices();
+        if (bondedDevices == null) {
+            errorLog("autoConnectA2dpSink, bondedDevices are null");
+            return;
+        }
+
+        for (BluetoothDevice device : bondedDevices) {
+             int priority = a2dpSinkService.getPriority(device);
+             debugLog("autoConnectA2dpSink, attempt auto-connect with device " + device
+                     + " priority " + priority);
+            if (priority == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+                debugLog("autoConnectA2dpSink() - Connecting A2DP Sink with " + device.toString());
+                a2dpSinkService.connect(device);
+            }
+        }
+    }
+
+    private boolean isConnectTimeoutDelayApplicable(BluetoothDevice device){
+        boolean isConnectionTimeoutDelayed = false;
+        String deviceAddress = device.getAddress();
+        for (int i = 0; i < delayConnectTimeoutDevice.length;i++) {
+            if (deviceAddress.indexOf(delayConnectTimeoutDevice[i]) == 0) {
+                isConnectionTimeoutDelayed = true;
+            }
+        }
+        return isConnectionTimeoutDelayed;
+    }
+
+    private boolean isConnectReducedTimeoutDelayApplicable(BluetoothDevice device){
+        boolean isConnectionReducedTimeoutDelayed = false;
+        String deviceAddress = device.getAddress();
+        for (int i = 0; i < delayReducedConnectTimeoutDevice.length;i++) {
+            if (deviceAddress.indexOf(delayReducedConnectTimeoutDevice[i]) == 0) {
+                isConnectionReducedTimeoutDelayed = true;
+            }
+        }
+        return isConnectionReducedTimeoutDelayed;
+    }
+
     private void connectOtherProfile(BluetoothDevice device) {
         if (mAdapterService.isQuietModeEnabled()) {
             debugLog("connectOtherProfile: in quiet mode, skip connect other profile " + device);
@@ -397,7 +560,12 @@
         mConnectOtherProfilesDeviceSet.add(device);
         Message m = mHandler.obtainMessage(MESSAGE_CONNECT_OTHER_PROFILES);
         m.obj = device;
-        mHandler.sendMessageDelayed(m, sConnectOtherProfilesTimeoutMillis);
+        if (isConnectTimeoutDelayApplicable(device))
+            mHandler.sendMessageDelayed(m,CONNECT_OTHER_PROFILES_TIMEOUT_DELAYED);
+        else if (isConnectReducedTimeoutDelayApplicable(device))
+            mHandler.sendMessageDelayed(m,CONNECT_OTHER_PROFILES_REDUCED_TIMEOUT_DELAYED);
+        else
+            mHandler.sendMessageDelayed(m, sConnectOtherProfilesTimeoutMillis);
     }
 
     // This function is called whenever a profile is connected.  This allows any other bluetooth
@@ -413,10 +581,15 @@
         HeadsetService hsService = mFactory.getHeadsetService();
         A2dpService a2dpService = mFactory.getA2dpService();
         PanService panService = mFactory.getPanService();
+        A2dpSinkService a2dpSinkService = mFactory.getA2dpSinkService();
+
+        boolean a2dpConnected = false;
+        boolean hsConnected = false;
 
         boolean atLeastOneProfileConnectedForDevice = false;
         boolean allProfilesEmpty = true;
         List<BluetoothDevice> a2dpConnDevList = null;
+        List<BluetoothDevice> a2dpSinkConnDevList = null;
         List<BluetoothDevice> hsConnDevList = null;
         List<BluetoothDevice> panConnDevList = null;
 
@@ -430,6 +603,11 @@
             allProfilesEmpty &= a2dpConnDevList.isEmpty();
             atLeastOneProfileConnectedForDevice |= a2dpConnDevList.contains(device);
         }
+        if (a2dpSinkService != null) {
+            a2dpSinkConnDevList = a2dpSinkService.getConnectedDevices();
+            allProfilesEmpty &= a2dpSinkConnDevList.isEmpty();
+            atLeastOneProfileConnectedForDevice |= a2dpSinkConnDevList.contains(device);
+        }
         if (panService != null) {
             panConnDevList = panService.getConnectedDevices();
             allProfilesEmpty &= panConnDevList.isEmpty();
@@ -449,22 +627,93 @@
             return;
         }
 
-        if (hsService != null) {
-            if (!mHeadsetRetrySet.contains(device) && (hsService.getPriority(device)
-                    >= BluetoothProfile.PRIORITY_ON) && (hsService.getConnectionState(device)
-                    == BluetoothProfile.STATE_DISCONNECTED)) {
-                debugLog("Retrying connection to Headset with device " + device);
-                mHeadsetRetrySet.add(device);
-                hsService.connect(device);
+        if(a2dpConnDevList != null && !a2dpConnDevList.isEmpty()) {
+            for (BluetoothDevice a2dpDevice : a2dpConnDevList)
+            {
+                if(a2dpDevice.equals(device))
+                {
+                    a2dpConnected = true;
+                }
             }
         }
+
+        if(hsConnDevList != null && !hsConnDevList.isEmpty()) {
+            for (BluetoothDevice hsDevice : hsConnDevList)
+            {
+                if(hsDevice.equals(device))
+                {
+                    hsConnected = true;
+                }
+            }
+        }
+
+        // This change makes sure that we try to re-connect
+        // the profile if its connection failed and priority
+        // for desired profile is ON.
+        debugLog("HF connected for device : " + device + " " +
+                (hsConnDevList == null ? false :hsConnDevList.contains(device)));
+        debugLog("A2DP connected for device : " + device + " " +
+                (a2dpConnDevList == null ? false :a2dpConnDevList.contains(device)));
+        debugLog("A2DPSink connected for device : " + device + " " +
+                (a2dpSinkConnDevList == null ? false :a2dpSinkConnDevList.contains(device)));
+
+        if (hsService != null) {
+            if ((hsConnDevList.isEmpty() || !(hsConnDevList.contains(device)))
+                    && (!mHeadsetRetrySet.contains(device))
+                    && (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_ON)
+                    && (hsService.getConnectionState(device)
+                               == BluetoothProfile.STATE_DISCONNECTED)
+                    && (a2dpConnected || (a2dpService != null &&
+                        a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_OFF))) {
+
+                debugLog("Retrying connection to HS with device " + device);
+                int maxConnections = mAdapterService.getMaxConnectedAudioDevices();
+
+                if (!hsConnDevList.isEmpty() && maxConnections == 1) {
+                    Log.v(TAG,"HFP is already connected, ignore");
+                    return;
+                }
+
+                // proceed connection only if a2dp is connected to this device
+                // add here as if is already overloaded
+                if (a2dpConnDevList.contains(device) ||
+                     (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_ON)) {
+                    debugLog("Retrying connection to HS with device " + device);
+                    mHeadsetRetrySet.add(device);
+                    hsService.connect(device);
+                } else {
+                    debugLog("do not initiate connect as A2dp is not connected");
+                }
+            }
+        }
+
         if (a2dpService != null) {
-            if (!mA2dpRetrySet.contains(device) && (a2dpService.getPriority(device)
-                    >= BluetoothProfile.PRIORITY_ON) && (a2dpService.getConnectionState(device)
-                    == BluetoothProfile.STATE_DISCONNECTED)) {
+            if ((a2dpConnDevList.isEmpty() || !(a2dpConnDevList.contains(device)))
+                    && (!mA2dpRetrySet.contains(device))
+                    && (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_ON ||
+                        mAdapterService.isTwsPlusDevice(device))
+                    && (a2dpService.getConnectionState(device)
+                               == BluetoothProfile.STATE_DISCONNECTED)
+                    && (hsConnected || (hsService != null &&
+                        hsService.getPriority(device) == BluetoothProfile.PRIORITY_OFF))) {
                 debugLog("Retrying connection to A2DP with device " + device);
-                mA2dpRetrySet.add(device);
-                a2dpService.connect(device);
+                int maxConnections = mAdapterService.getMaxConnectedAudioDevices();
+
+                if (!a2dpConnDevList.isEmpty() && maxConnections == 1) {
+                    Log.v(TAG,"a2dp is already connected, ignore");
+                    return;
+                }
+
+                // proceed connection only if HFP is connected to this device
+                // add here as if is already overloaded
+                if (hsConnDevList.contains(device) ||
+                    (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_ON)) {
+                    debugLog("Retrying connection to A2DP with device " + device);
+                    mA2dpRetrySet.add(device);
+                    a2dpService.connect(device);
+                } else {
+                    debugLog("do not initiate connect as HFP is not connected");
+                }
             }
         }
         if (panService != null) {
@@ -477,8 +726,67 @@
                 panService.connect(device);
             }
         }
+        // Connect A2DP Sink Service if HS is connected
+        if (a2dpSinkService != null) {
+            List<BluetoothDevice> sinkConnDevList = a2dpSinkService.getConnectedDevices();
+            if (sinkConnDevList.isEmpty() &&
+                    (a2dpSinkService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) &&
+                    (a2dpSinkService.getConnectionState(device) ==
+                            BluetoothProfile.STATE_DISCONNECTED) &&
+                    (hsConnected || (hsService != null &&
+                         hsService.getPriority(device) == BluetoothProfile.PRIORITY_OFF))) {
+                debugLog("Retrying connection for A2dpSink with device " + device);
+                a2dpSinkService.connect(device);
+            }
+        }
+
     }
 
+    private void setProfileAutoConnectionPriority(BluetoothDevice device, int profileId,
+            boolean autoConnect) {
+        debugLog("setProfileAutoConnectionPriority: device=" + device + ", profile=" + profileId
+                + ", autoConnect=" + autoConnect);
+        switch (profileId) {
+            case BluetoothProfile.HEADSET: {
+                HeadsetService hsService = mFactory.getHeadsetService();
+                if (hsService == null) {
+                    warnLog("setProfileAutoConnectionPriority: HEADSET service is null");
+                    break;
+                }
+                removeAutoConnectFromDisconnectedHeadsets(hsService);
+                if (autoConnect) {
+                    hsService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
+                }
+                break;
+            }
+            case BluetoothProfile.A2DP: {
+                A2dpService a2dpService = mFactory.getA2dpService();
+                if (a2dpService == null) {
+                    warnLog("setProfileAutoConnectionPriority: A2DP service is null");
+                    break;
+                }
+                removeAutoConnectFromDisconnectedA2dpSinks(a2dpService);
+                if (autoConnect) {
+                    a2dpService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
+                }
+                break;
+            }
+            case BluetoothProfile.A2DP_SINK: {
+                A2dpSinkService a2dpSinkService = mFactory.getA2dpSinkService();
+                if (a2dpSinkService != null) {
+                    if (BluetoothProfile.PRIORITY_AUTO_CONNECT != a2dpSinkService.getPriority(
+                            device)) {
+                        adjustOtherSourcePriorities(a2dpSinkService, a2dpSinkService.getConnectedDevices());
+                        a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
+                    }
+                }
+                break;
+            }
+            default:
+                Log.w(TAG, "Tried to set AutoConnect priority on invalid profile " + profileId);
+                break;
+        }
+    }
     /**
      * Set a device's headset profile priority to PRIORITY_AUTO_CONNECT if device support that
      * profile
@@ -515,6 +823,21 @@
     }
 
     /**
+     * Set device A2DP SINK priority to PRIORITY_AUTO_CONNECT if role is A2DP Sink
+     */
+    private void setAutoConnectForA2dpSource(BluetoothDevice device) {
+        A2dpSinkService a2dpSinkService = mFactory.getA2dpSinkService();
+        if (a2dpSinkService == null) {
+            warnLog("setAutoConnectForA2dpSink: A2DP service is null");
+            return;
+        }
+        if (a2dpSinkService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) {
+            debugLog("setAutoConnectForA2dpSink: device " + device + " PRIORITY_AUTO_CONNECT");
+            a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
+        }
+    }
+
+    /**
      * Remove PRIORITY_AUTO_CONNECT from all headsets and set headset that used to have
      * PRIORITY_AUTO_CONNECT to PRIORITY_ON
      *
@@ -550,6 +873,40 @@
         }
     }
 
+    private void adjustOtherSourcePriorities(
+            A2dpSinkService a2dpSinkService, List<BluetoothDevice> connectedDeviceList) {
+        for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
+            if (a2dpSinkService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT
+                    && !connectedDeviceList.contains(device)) {
+                a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        }
+    }
+
+    private void removeAutoConnectFromDisconnectedHeadsets(HeadsetService hsService) {
+        List<BluetoothDevice> connectedDeviceList = hsService.getConnectedDevices();
+        for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
+            if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT
+                    && !connectedDeviceList.contains(device)) {
+                debugLog("removeAutoConnectFromDisconnectedHeadsets, device " + device
+                        + " PRIORITY_ON");
+                hsService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        }
+    }
+
+    private void removeAutoConnectFromDisconnectedA2dpSinks(A2dpService a2dpService) {
+        List<BluetoothDevice> connectedDeviceList = a2dpService.getConnectedDevices();
+        for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
+            if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT
+                    && !connectedDeviceList.contains(device)) {
+                debugLog("removeAutoConnectFromDisconnectedA2dpSinks, device " + device
+                        + " PRIORITY_ON");
+                a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        }
+    }
+
     private static void debugLog(String msg) {
         if (DBG) {
             Log.i(TAG, msg);
diff --git a/src/com/android/bluetooth/btservice/ProfileService.java b/src/com/android/bluetooth/btservice/ProfileService.java
index 9a7186c..92cc261 100644
--- a/src/com/android/bluetooth/btservice/ProfileService.java
+++ b/src/com/android/bluetooth/btservice/ProfileService.java
@@ -54,7 +54,7 @@
     //Profile services will not be automatically restarted.
     //They must be explicitly restarted by AdapterService
     private static final int PROFILE_SERVICE_MODE = Service.START_NOT_STICKY;
-    private BluetoothAdapter mAdapter;
+    protected BluetoothAdapter mAdapter;
     private IProfileServiceBinder mBinder;
     private final String mName;
     private AdapterService mAdapterService;
@@ -130,7 +130,7 @@
         if (DBG) {
             Log.d(mName, "onStartCommand()");
         }
-
+        AdapterService adapterService = AdapterService.getAdapterService();
         if (checkCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM)
                 != PackageManager.PERMISSION_GRANTED) {
             Log.e(mName, "Permission denied!");
@@ -145,10 +145,31 @@
         String action = intent.getStringExtra(AdapterService.EXTRA_ACTION);
         if (AdapterService.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+            int currentState = (adapterService != null) ? adapterService.getState() : -1;
             if (state == BluetoothAdapter.STATE_OFF) {
-                doStop();
+                if ((currentState == BluetoothAdapter.STATE_TURNING_OFF &&
+                     !mName.equals("GattService")) ||
+                     (currentState == BluetoothAdapter.STATE_BLE_TURNING_OFF &&
+                     mName.equals("GattService")) ) {
+                    Log.d(mName, ": Received stop request...Stopping profile...");
+                    doStop();
+                } else {
+                    Log.e(mName, ":intent received late, not Stopping profile");
+                }
             } else if (state == BluetoothAdapter.STATE_ON) {
-                doStart();
+                if ((currentState == BluetoothAdapter.STATE_TURNING_ON &&
+                     !mName.equals("GattService")) ||
+                     (currentState == BluetoothAdapter.STATE_BLE_TURNING_ON &&
+                     mName.equals("GattService")) ) {
+
+                     Log.d(mName, "Received start request. Starting profile...");
+                     doStart();
+                } else {
+                     Log.e(mName, ":intent received late, not starting profile");
+                     if (adapterService != null) {
+                         adapterService.removeProfile(this);
+                     }
+                }
             }
         }
         return PROFILE_SERVICE_MODE;
@@ -265,6 +286,7 @@
             Log.e(mName, "Error starting profile. start() returned false.");
             return;
         }
+        Log.d(mName, " profile started successfully");
         mAdapterService.onProfileServiceStateChanged(this, BluetoothAdapter.STATE_ON);
     }
 
@@ -273,11 +295,13 @@
             Log.w(mName, "doStop() called, but the profile is not running.");
         }
         mProfileStarted = false;
-        if (mAdapterService != null) {
-            mAdapterService.onProfileServiceStateChanged(this, BluetoothAdapter.STATE_OFF);
-        }
         if (!stop()) {
             Log.e(mName, "Unable to stop profile");
+        } else {
+            Log.d(mName, " profile stopped successfully");
+        }
+        if (mAdapterService != null) {
+            mAdapterService.onProfileServiceStateChanged(this, BluetoothAdapter.STATE_OFF);
         }
         if (mAdapterService != null) {
             mAdapterService.removeProfile(this);
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index 12897fe..356c8b1 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -1,4 +1,39 @@
 /*
+ * Copyright (C) 2017, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted (subject to the limitations in the
+ * disclaimer below) provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer in the documentation and/or other materials provided
+ *     with the distribution.
+ *
+ * * Neither the name of The Linux Foundation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
+ * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
+ * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
  * Copyright (C) 2012-2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -107,6 +142,12 @@
         }
     };
 
+    public static final String ACTION_TWS_PLUS_DEVICE_PAIR =
+        "android.bluetooth.device.action.TWS_PLUS_DEVICE_PAIR";
+    public static final String EXTRA_TWS_PLUS_DEVICE1 =
+        "android.bluetooth.device.extra.EXTRA_TWS_PLUS_DEVICE1";
+    public static final String EXTRA_TWS_PLUS_DEVICE2 =
+        "android.bluetooth.device.extra.EXTRA_TWS_PLUS_DEVICE2";
     RemoteDevices(AdapterService service, Looper looper) {
         sAdapter = BluetoothAdapter.getDefaultAdapter();
         sAdapterService = service;
@@ -170,11 +211,14 @@
     }
 
     BluetoothDevice getDevice(byte[] address) {
-        DeviceProperties prop = mDevices.get(Utils.getAddressStringFromByte(address));
-        if (prop == null) {
-            return null;
+        DeviceProperties prop;
+        synchronized (mDevices) {
+            prop = mDevices.get(Utils.getAddressStringFromByte(address));
         }
-        return prop.getDevice();
+        if (prop != null) {
+            return prop.getDevice();
+        }
+        return null;
     }
 
     DeviceProperties addDeviceProperties(byte[] address) {
@@ -214,9 +258,15 @@
         private BluetoothDevice mDevice;
         private boolean mIsBondingInitiatedLocally;
         private int mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+        private short mTwsPlusDevType;
+        private byte[] peerEbAddress;
+        private boolean autoConnect;
 
         DeviceProperties() {
             mBondState = BluetoothDevice.BOND_NONE;
+            mTwsPlusDevType = AbstractionLayer.TWS_PLUS_DEV_TYPE_NONE;
+            autoConnect = true;
+            peerEbAddress = null;
         }
 
         /**
@@ -297,6 +347,8 @@
         void setAlias(BluetoothDevice device, String mAlias) {
             synchronized (mObject) {
                 this.mAlias = mAlias;
+                if (mAlias == null)
+                    return;
                 sAdapterService.setDevicePropertyNative(mAddress,
                         AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME, mAlias.getBytes());
                 Intent intent = new Intent(BluetoothDevice.ACTION_ALIAS_CHANGED);
@@ -307,6 +359,72 @@
         }
 
         /**
+         * @return mTwsPlusDevType
+         */
+        int getTwsPlusDevType() {
+            synchronized (mObject) {
+                return mTwsPlusDevType;
+            }
+        }
+
+        /**
+         * @return peerEbAddress
+         */
+        byte[] getTwsPlusPeerAddress() {
+            synchronized (mObject) {
+                return peerEbAddress;
+            }
+        }
+
+        /**
+         * @param mTwsPlusDevType the mTwsPlusDevType to set
+         */
+        void setTwsPlusDevType(short twsPlusDevType) {
+            synchronized (mObject) {
+                this.mTwsPlusDevType = twsPlusDevType;
+                if(twsPlusDevType == AbstractionLayer.TWS_PLUS_DEV_TYPE_NONE) {
+                   this.peerEbAddress = null;
+                }
+            }
+        }
+
+        /**
+         * @param peerEbAddress the peerEbAddress to set
+         */
+        void setTwsPlusPeerEbAddress(BluetoothDevice device, byte[] peerEbAddress) {
+            synchronized (mObject) {
+                Intent intent;
+
+                /* in case of null null bd address reset the address */
+                if (peerEbAddress != null &&
+                    Utils.getAddressStringFromByte(peerEbAddress).equals("00:00:00:00:00:00")) {
+                    this.peerEbAddress = null;
+                    errorLog(" resetting the peerEbAddress to null");
+                } else {
+                    this.peerEbAddress = peerEbAddress;
+                    if(device != null && peerEbAddress != null) {
+                        errorLog(" Peer EB Address is:" +
+                                Utils.getAddressStringFromByte(peerEbAddress));
+                        intent = new Intent(ACTION_TWS_PLUS_DEVICE_PAIR);
+                        intent.putExtra(EXTRA_TWS_PLUS_DEVICE1, mDevice);
+                        intent.putExtra(EXTRA_TWS_PLUS_DEVICE2, device);
+                        sAdapterService.sendBroadcast(intent,
+                                AdapterService.BLUETOOTH_ADMIN_PERM);
+                    }
+                }
+            }
+        }
+
+        /**
+         * @param peerEbAddress the peerEbAddress to set
+         */
+        void setTwsPlusAutoConnect(BluetoothDevice device, boolean autoConnect) {
+            synchronized (mObject) {
+                this.autoConnect = autoConnect;
+                debugLog("sendUuidIntent as Auto connect  " + autoConnect );
+            }
+        }
+        /*
          * @param mBondState the mBondState to set
          */
         void setBondState(int mBondState) {
@@ -373,7 +491,9 @@
         sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_ADMIN_PERM);
 
         //Remove the outstanding UUID request
-        sSdpTracker.remove(device);
+        if (sSdpTracker.contains(device)) {
+            sSdpTracker.remove(device);
+        }
     }
 
     /**
@@ -487,6 +607,11 @@
             device = getDeviceProperties(bdDevice);
         }
 
+        if (device == null) {
+            errorLog("device null ");
+            return;
+        }
+
         if (types.length <= 0) {
             errorLog("No properties to update");
             return;
@@ -544,7 +669,9 @@
                                 break;
                             }
                             device.mUuids = newUuids;
-                            if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) {
+                            if ((sAdapterService.getState() == BluetoothAdapter.STATE_ON) &&
+                                                            device.autoConnect ) {
+                                debugLog("sendUuidIntent as Auto connect is set ");
                                 sendUuidIntent(bdDevice);
                             }
                             break;
@@ -608,13 +735,6 @@
                     "aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state)
                             + " Connected: " + device);
         } else {
-            if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
-                // Send PAIRING_CANCEL intent to dismiss any dialog requesting bonding.
-                intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL);
-                intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-                intent.setPackage(sAdapterService.getString(R.string.pairing_ui_package));
-                sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
-            }
             if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_TURNING_OFF) {
                 intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
             } else if (state == BluetoothAdapter.STATE_BLE_ON
diff --git a/src/com/android/bluetooth/btservice/ServiceFactory.java b/src/com/android/bluetooth/btservice/ServiceFactory.java
index 2bd7ab5..580de3b 100644
--- a/src/com/android/bluetooth/btservice/ServiceFactory.java
+++ b/src/com/android/bluetooth/btservice/ServiceFactory.java
@@ -17,6 +17,7 @@
 package com.android.bluetooth.btservice;
 
 import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
 import com.android.bluetooth.hearingaid.HearingAidService;
 import com.android.bluetooth.hfp.HeadsetService;
 import com.android.bluetooth.hid.HidDeviceService;
@@ -48,4 +49,8 @@
     public HearingAidService getHearingAidService() {
         return HearingAidService.getHearingAidService();
     }
+
+    public A2dpSinkService getA2dpSinkService() {
+        return A2dpSinkService.getA2dpSinkService();
+    }
 }
diff --git a/src/com/android/bluetooth/gatt/AdvertiseHelper.java b/src/com/android/bluetooth/gatt/AdvertiseHelper.java
index 1c1b4dc..a71de5b 100644
--- a/src/com/android/bluetooth/gatt/AdvertiseHelper.java
+++ b/src/com/android/bluetooth/gatt/AdvertiseHelper.java
@@ -39,6 +39,7 @@
     private static final int SERVICE_DATA_32_BIT_UUID = 0X20;
     private static final int SERVICE_DATA_128_BIT_UUID = 0X21;
     private static final int MANUFACTURER_SPECIFIC_DATA = 0XFF;
+    private static final int TRANSPORT_DISCOVERY_DATA = 0X26;
 
     public static byte[] advertiseDataToBytes(AdvertiseData data, String name) {
 
@@ -166,6 +167,13 @@
             }
         }
 
+        byte[] transportDiscoveryData = data.getTransportDiscoveryData();
+        if (transportDiscoveryData != null && (transportDiscoveryData.length > 0)) {
+            ret.write(transportDiscoveryData.length + 1);
+            ret.write(TRANSPORT_DISCOVERY_DATA);
+            ret.write(transportDiscoveryData, 0, transportDiscoveryData.length);
+        }
+
         return ret.toByteArray();
     }
 }
diff --git a/src/com/android/bluetooth/gatt/AdvertiseManager.java b/src/com/android/bluetooth/gatt/AdvertiseManager.java
index 85917a4..11a8ba1 100644
--- a/src/com/android/bluetooth/gatt/AdvertiseManager.java
+++ b/src/com/android/bluetooth/gatt/AdvertiseManager.java
@@ -394,6 +394,29 @@
         callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status);
     }
 
+    void stopAdvertisingSets() {
+        Log.d(TAG, "stopAdvertisingSets()");
+        for (Map.Entry<IBinder, AdvertiserInfo> entry : mAdvertisers.entrySet()) {
+            Integer advertiser_id = entry.getValue().id;
+            IAdvertisingSetCallback callback = entry.getValue().callback;
+            IBinder binder = toBinder(callback);
+            binder.unlinkToDeath(entry.getValue().deathRecipient, 0);
+
+            if (advertiser_id < 0) {
+                Log.i(TAG, "stopAdvertisingSets() - advertiser not finished registration yet");
+                continue;
+            }
+
+            stopAdvertisingSetNative(advertiser_id);
+
+            try {
+                callback.onAdvertisingSetStopped(advertiser_id);
+            } catch (RemoteException e) {
+                Log.i(TAG, "error sending onAdvertisingSetStopped callback", e);
+            }
+        }
+    }
+
     static {
         classInitNative();
     }
diff --git a/src/com/android/bluetooth/gatt/ContextMap.java b/src/com/android/bluetooth/gatt/ContextMap.java
index af59262..262e76e 100644
--- a/src/com/android/bluetooth/gatt/ContextMap.java
+++ b/src/com/android/bluetooth/gatt/ContextMap.java
@@ -26,6 +26,8 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Collections;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -149,10 +151,10 @@
     }
 
     /** Our internal application list */
-    private List<App> mApps = new ArrayList<App>();
+    private List<App> mApps = Collections.synchronizedList(new ArrayList<App>());
 
     /** Internal map to keep track of logging information by app name */
-    HashMap<Integer, AppScanStats> mAppScanStats = new HashMap<Integer, AppScanStats>();
+    Map<Integer, AppScanStats> mAppScanStats = new ConcurrentHashMap<Integer, AppScanStats>();
 
     /** Internal list of connected devices **/
     Set<Connection> mConnections = new HashSet<Connection>();
@@ -251,7 +253,6 @@
                 Connection connection = i.next();
                 if (connection.connId == connId) {
                     i.remove();
-                    break;
                 }
             }
         }
diff --git a/src/com/android/bluetooth/gatt/GattDebugUtils.java b/src/com/android/bluetooth/gatt/GattDebugUtils.java
index 232477b..cc6ab3b 100644
--- a/src/com/android/bluetooth/gatt/GattDebugUtils.java
+++ b/src/com/android/bluetooth/gatt/GattDebugUtils.java
@@ -73,6 +73,10 @@
             return false;
         }
 
+        if (intent == null) {
+            Log.d(TAG, "received NULL intent in GattService. Return.");
+            return true;
+        }
         String action = intent.getAction();
         Log.d(TAG, "handleDebugAction() action=" + action);
 
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index fd8551a..7040307 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -172,6 +172,7 @@
     private AppOpsManager mAppOps;
 
     private static GattService sGattService;
+    private boolean mNativeAvailable;
 
     /**
      * Reliable write queue
@@ -179,6 +180,8 @@
     private Set<String> mReliableQueue = new HashSet<String>();
 
     static {
+        if (DBG) Log.d(TAG, "classInitNative called");
+        System.loadLibrary("bluetooth_jni");
         classInitNative();
     }
 
@@ -193,6 +196,7 @@
             Log.d(TAG, "start()");
         }
         initializeNative();
+        mNativeAvailable = true;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         mAppOps = getSystemService(AppOpsManager.class);
         mAdvertiseManager = new AdvertiseManager(this, AdapterService.getAdapterService());
@@ -219,14 +223,18 @@
         mServerMap.clear();
         mHandleMap.clear();
         mReliableQueue.clear();
-        if (mAdvertiseManager != null) {
-            mAdvertiseManager.cleanup();
-        }
-        if (mScanManager != null) {
-            mScanManager.cleanup();
-        }
-        if (mPeriodicScanManager != null) {
-            mPeriodicScanManager.cleanup();
+        if (mNativeAvailable) {
+            mNativeAvailable = false;
+            cleanupNative();
+            if (mAdvertiseManager != null) {
+                mAdvertiseManager.cleanup();
+            }
+            if (mScanManager != null) {
+                mScanManager.cleanup();
+            }
+            if (mPeriodicScanManager != null) {
+                mPeriodicScanManager.cleanup();
+            }
         }
         return true;
     }
@@ -236,15 +244,19 @@
         if (DBG) {
             Log.d(TAG, "cleanup()");
         }
-        cleanupNative();
-        if (mAdvertiseManager != null) {
-            mAdvertiseManager.cleanup();
-        }
-        if (mScanManager != null) {
-            mScanManager.cleanup();
-        }
-        if (mPeriodicScanManager != null) {
-            mPeriodicScanManager.cleanup();
+
+        if (mNativeAvailable) {
+            mNativeAvailable = false;
+            cleanupNative();
+            if (mAdvertiseManager != null) {
+                mAdvertiseManager.cleanup();
+            }
+            if (mScanManager != null) {
+                mScanManager.cleanup();
+            }
+            if (mPeriodicScanManager != null) {
+                mPeriodicScanManager.cleanup();
+            }
         }
     }
 
@@ -2012,8 +2024,9 @@
         if (app != null) {
             app.recordScanStop(client.scannerId);
         }
-
-        mScanManager.stopScan(client);
+        if (mScanManager != null) {
+            mScanManager.stopScan(client);
+        }
     }
 
     void stopScan(PendingIntent intent, String callingPackage) {
@@ -2046,6 +2059,20 @@
         }
     }
 
+    boolean isScanClient(int clientIf) {
+        for (ScanClient client : mScanManager.getRegularScanQueue()) {
+            if (client.scannerId == clientIf) {
+                return true;
+            }
+        }
+        for (ScanClient client : mScanManager.getBatchScanQueue()) {
+            if (client.scannerId == clientIf) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     void unregAll() {
         for (Integer appId : mClientMap.getAllAppsIds()) {
             if (DBG) {
@@ -2053,6 +2080,25 @@
             }
             unregisterClient(appId);
         }
+        for (Integer appId : mServerMap.getAllAppsIds()) {
+            if (DBG) Log.d(TAG, "unreg:" + appId);
+            unregisterServer(appId);
+        }
+        for (Integer appId : mScannerMap.getAllAppsIds()) {
+            if (DBG) Log.d(TAG, "unreg:" + appId);
+            if (isScanClient(appId)) {
+                ScanClient client = new ScanClient(appId);
+                stopScan(client);
+                unregisterScanner(appId);
+            }
+        }
+        if (mAdvertiseManager != null) {
+            mAdvertiseManager.stopAdvertisingSets();
+        }
+    }
+
+    public void setAptXLowLatencyMode(boolean enabled){
+        mScanManager.setAptXLowLatencyMode(enabled);
     }
 
     /**************************************************************************
diff --git a/src/com/android/bluetooth/gatt/ScanFilterQueue.java b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
index 15233e4..6c47711 100644
--- a/src/com/android/bluetooth/gatt/ScanFilterQueue.java
+++ b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
@@ -95,6 +95,15 @@
         Entry entry = new Entry();
         entry.type = TYPE_SOLICIT_UUID;
         entry.uuid = uuid;
+        entry.uuid_mask = new UUID(0, 0);
+        mEntries.add(entry);
+    }
+
+    void addSolicitUuid(UUID uuid, UUID uuidMask) {
+        Entry entry = new Entry();
+        entry.type = TYPE_SOLICIT_UUID;
+        entry.uuid = uuid;
+        entry.uuid_mask = uuidMask;
         mEntries.add(entry);
     }
 
@@ -179,6 +188,14 @@
                 addUuid(filter.getServiceUuid().getUuid(), filter.getServiceUuidMask().getUuid());
             }
         }
+        if (filter.getServiceSolicitationUuid() != null) {
+            if (filter.getServiceSolicitationUuidMask() == null) {
+                addSolicitUuid(filter.getServiceSolicitationUuid().getUuid());
+            } else {
+                addSolicitUuid(filter.getServiceSolicitationUuid().getUuid(),
+                        filter.getServiceSolicitationUuidMask().getUuid());
+            }
+        }
         if (filter.getManufacturerData() != null) {
             if (filter.getManufacturerDataMask() == null) {
                 addManufacturerData(filter.getManufacturerId(), filter.getManufacturerData());
diff --git a/src/com/android/bluetooth/gatt/ScanManager.java b/src/com/android/bluetooth/gatt/ScanManager.java
index 5207c69..926837b 100644
--- a/src/com/android/bluetooth/gatt/ScanManager.java
+++ b/src/com/android/bluetooth/gatt/ScanManager.java
@@ -20,6 +20,7 @@
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.le.ScanCallback;
 import android.bluetooth.le.ScanFilter;
 import android.bluetooth.le.ScanSettings;
@@ -49,6 +50,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
@@ -77,13 +79,19 @@
     private static final int MSG_SUSPEND_SCANS = 4;
     private static final int MSG_RESUME_SCANS = 5;
     private static final int MSG_IMPORTANCE_CHANGE = 6;
+    /*For suspending both unfiltered & filtered scans*/
+    private static final int MSG_SUSPEND_SCAN_ALL = 7;
+    /* To handle display changed events in Handler thread context to avoid ANR */
+    private static final int MSG_HANDLE_DISPLAY_CHANGED = 8;
     private static final String ACTION_REFRESH_BATCHED_SCAN =
             "com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN";
 
+
     // Timeout for each controller operation.
     private static final int OPERATION_TIME_OUT_MILLIS = 500;
 
-    private int mLastConfiguredScanSetting = Integer.MIN_VALUE;
+    private int mLastConfiguredScanSettingLE1M = Integer.MIN_VALUE;
+    private int mLastConfiguredScanSettingLECoded = Integer.MIN_VALUE;
     // Scan parameters for batch scan.
     private BatchScanParams mBatchScanParms;
 
@@ -91,6 +99,7 @@
     private GattService mService;
     private BroadcastReceiver mBatchAlarmReceiver;
     private boolean mBatchAlarmReceiverRegistered;
+    private boolean mIsAptXLowLatencyModeEnabled;
     private ScanNative mScanNative;
     private volatile ClientHandler mHandler;
 
@@ -117,6 +126,18 @@
         }
     }
 
+    private class PhyInfo {
+        public int scanPhy;
+        public int scanModeLE1M;
+        public int scanModeLECoded;
+
+        PhyInfo(int scanPhy, int scanModeLE1M, int scanModeLECoded) {
+            this.scanPhy = scanPhy;
+            this.scanModeLE1M = scanModeLE1M;
+            this.scanModeLECoded = scanModeLECoded;
+        }
+    };
+
     ScanManager(GattService service) {
         mRegularScanClients =
                 Collections.newSetFromMap(new ConcurrentHashMap<ScanClient, Boolean>());
@@ -267,6 +288,7 @@
 
         @Override
         public void handleMessage(Message msg) {
+            Log.i(TAG, "msg.what = " + msg.what);
             switch (msg.what) {
                 case MSG_START_BLE_SCAN:
                     handleStartScan((ScanClient) msg.obj);
@@ -289,6 +311,12 @@
                 case MSG_IMPORTANCE_CHANGE:
                     handleImportanceChange((UidImportance) msg.obj);
                     break;
+                case MSG_SUSPEND_SCAN_ALL:
+                    handleSuspendScanAll();
+                    break;
+                case MSG_HANDLE_DISPLAY_CHANGED:
+                    handleScanOnDisplayChanged();
+                    break;
                 default:
                     // Shouldn't happen.
                     Log.e(TAG, "received an unkown message : " + msg.what);
@@ -333,13 +361,28 @@
                 return;
             }
 
+            if (isAptXLowLatencyModeEnabled()) {
+                Log.i(TAG, "Cannot start Scan when aptX LL mode is enabled. This scan will be"
+                        + " resumed when aptX LL mode is disabled: " + client.scannerId);
+                mSuspendedScanClients.add(client);
+                if (client.stats != null) {
+                    client.stats.recordScanSuspend(client.scannerId);
+                }
+                return;
+            }
+
             // Begin scan operations.
             if (isBatchClient(client)) {
                 mBatchClients.add(client);
                 mScanNative.startBatchScan(client);
             } else {
                 mRegularScanClients.add(client);
-                mScanNative.startRegularScan(client);
+                boolean ret = mScanNative.startRegularScan(client);
+                if (!ret) {
+                    mRegularScanClients.remove(client);
+                    return;
+                }
+
                 if (!mScanNative.isOpportunisticScanClient(client)) {
                     mScanNative.configureRegularScanParams();
 
@@ -355,6 +398,8 @@
 
         void handleStopScan(ScanClient client) {
             Utils.enforceAdminPermission(mService);
+            boolean appDied;
+            int scannerId;
             if (client == null) {
                 return;
             }
@@ -363,6 +408,22 @@
                 mSuspendedScanClients.remove(client);
             }
 
+            // The caller may pass a dummy client with only clientIf
+            // and appDied status. Perform the operation on the
+            // actual client in that case.
+            appDied = client.appDied;
+            scannerId = client.scannerId;
+            client = mScanNative.getRegularScanClient(scannerId);
+            if (client == null) {
+                if (DBG) Log.d(TAG, "regular client is null");
+                client = mScanNative.getBatchScanClient(scannerId);
+
+                if(client == null) {
+                    if (DBG) Log.d(TAG,"batch client is null");
+                    return;
+                }
+            }
+
             if (mRegularScanClients.contains(client)) {
                 mScanNative.stopRegularScan(client);
 
@@ -373,10 +434,10 @@
                 if (!mScanNative.isOpportunisticScanClient(client)) {
                     mScanNative.configureRegularScanParams();
                 }
-            } else {
+            } else if (mBatchClients.contains(client)) {
                 mScanNative.stopBatchScan(client);
             }
-            if (client.appDied) {
+            if (appDied) {
                 if (DBG) {
                     Log.d(TAG, "app died, unregister scanner - " + client.scannerId);
                 }
@@ -426,6 +487,18 @@
                 }
             }
         }
+        void handleSuspendScanAll() {
+            for (ScanClient client : mRegularScanClients) {
+                if (!mScanNative.isOpportunisticScanClient(client)) {
+                    /*Suspend both unfiltered & filtered scans*/
+                    if (client.stats != null) {
+                        client.stats.recordScanSuspend(client.scannerId);
+                    }
+                    handleStopScan(client);
+                    mSuspendedScanClients.add(client);
+                }
+            }
+        }
 
         void handleResumeScans() {
             for (ScanClient client : mSuspendedScanClients) {
@@ -477,6 +550,7 @@
         private static final int DELIVERY_MODE_IMMEDIATE = 0;
         private static final int DELIVERY_MODE_ON_FOUND_LOST = 1;
         private static final int DELIVERY_MODE_BATCH = 2;
+        private static final int DELIVERY_MODE_ROUTE = 8;
 
         private static final int ONFOUND_SIGHTINGS_AGGRESSIVE = 1;
         private static final int ONFOUND_SIGHTINGS_STICKY = 4;
@@ -535,6 +609,7 @@
 
             mAlarmManager = (AlarmManager) mService.getSystemService(Context.ALARM_SERVICE);
             Intent batchIntent = new Intent(ACTION_REFRESH_BATCHED_SCAN, null);
+            batchIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
             mBatchScanIntervalIntent = PendingIntent.getBroadcast(mService, 0, batchIntent, 0);
             IntentFilter filter = new IntentFilter();
             filter.addAction(ACTION_REFRESH_BATCHED_SCAN);
@@ -548,9 +623,13 @@
                         if (mBatchClients.isEmpty()) {
                             return;
                         }
-                        // Note this actually flushes all pending batch data.
-                        if (mBatchClients.iterator().hasNext()) {
-                            flushBatchScanResults(mBatchClients.iterator().next());
+                        try {
+                            // Note this actually flushes all pending batch data.
+                            if (mBatchClients.iterator().hasNext()) {
+                                flushBatchScanResults(mBatchClients.iterator().next());
+                            }
+                        } catch (NoSuchElementException e) {
+                            Log.e(TAG, "Element not found: " + e);
                         }
                     }
                 }
@@ -576,40 +655,94 @@
             if (DBG) {
                 Log.d(TAG, "configureRegularScanParams() - queue=" + mRegularScanClients.size());
             }
-            int curScanSetting = Integer.MIN_VALUE;
+            int curScanSettingLE1M = Integer.MIN_VALUE;
+            int curScanSettingLECoded = Integer.MIN_VALUE;
+            int scanPhy = BluetoothDevice.PHY_LE_1M;
             ScanClient client = getAggressiveClient(mRegularScanClients);
-            if (client != null) {
-                curScanSetting = client.settings.getScanMode();
+            PhyInfo phyInfoResult = getPhyInfo(mRegularScanClients);
+            boolean scanModeChanged = false;
+            int scanWindowLE1M = Integer.MIN_VALUE;
+            int scanIntervalLE1M = Integer.MIN_VALUE;
+            int scanWindowLECoded = Integer.MIN_VALUE;
+            int scanIntervalLECoded = Integer.MIN_VALUE;
+            int[] scanInterval = new int[2];
+            int[] scanWindow  = new int[2];
+            int phyCnt = 0;
+
+
+            if (phyInfoResult != null) {
+                curScanSettingLE1M = phyInfoResult.scanModeLE1M;
+                curScanSettingLECoded = phyInfoResult.scanModeLECoded;
+                scanPhy = phyInfoResult.scanPhy;
             }
 
             if (DBG) {
-                Log.d(TAG, "configureRegularScanParams() - ScanSetting Scan mode=" + curScanSetting
-                        + " mLastConfiguredScanSetting=" + mLastConfiguredScanSetting);
+                Log.d(TAG, "configureRegularScanParams() - ScanSetting LE 1M Scan mode=" + curScanSettingLE1M
+                        + "ScanSetting LE Coded Scan mode=" + curScanSettingLECoded
+                        + " mLastConfiguredScanSettingLE1M=" + mLastConfiguredScanSettingLE1M
+                        + " mLastConfiguredScanSettingLECoded=" + mLastConfiguredScanSettingLECoded
+                        + "scanPhy=" + scanPhy);
             }
 
-            if (curScanSetting != Integer.MIN_VALUE
-                    && curScanSetting != ScanSettings.SCAN_MODE_OPPORTUNISTIC) {
-                if (curScanSetting != mLastConfiguredScanSetting) {
-                    int scanWindow = getScanWindowMillis(client.settings);
-                    int scanInterval = getScanIntervalMillis(client.settings);
+            /* Check whether scan mode for LE 1M PHY has changed and compute the appropriate
+             * scan interval and scan window values
+             */
+            if ((curScanSettingLE1M != Integer.MIN_VALUE
+                    && curScanSettingLE1M != ScanSettings.SCAN_MODE_OPPORTUNISTIC)) {
+                if ((curScanSettingLE1M != mLastConfiguredScanSettingLE1M) ||
+                       (curScanSettingLECoded != mLastConfiguredScanSettingLECoded)) {
+                    scanModeChanged = true;
+                    ScanSettings settings = new ScanSettings.Builder().setScanMode(curScanSettingLE1M).build();
+                    scanWindowLE1M = getScanWindowMillis(settings);
+                    scanIntervalLE1M = getScanIntervalMillis(settings);
                     // convert scanWindow and scanInterval from ms to LE scan units(0.625ms)
-                    scanWindow = Utils.millsToUnit(scanWindow);
-                    scanInterval = Utils.millsToUnit(scanInterval);
-                    gattClientScanNative(false);
-                    if (DBG) {
-                        Log.d(TAG, "configureRegularScanParams - scanInterval = " + scanInterval
-                                + "configureRegularScanParams - scanWindow = " + scanWindow);
-                    }
-                    gattSetScanParametersNative(client.scannerId, scanInterval, scanWindow);
-                    gattClientScanNative(true);
-                    mLastConfiguredScanSetting = curScanSetting;
+                    scanWindow[phyCnt] = Utils.millsToUnit(scanWindowLE1M);
+                    scanInterval[phyCnt] = Utils.millsToUnit(scanIntervalLE1M);
+                    phyCnt++;
                 }
-            } else {
-                mLastConfiguredScanSetting = curScanSetting;
+            }
+            else {
+                mLastConfiguredScanSettingLE1M = curScanSettingLE1M;
                 if (DBG) {
                     Log.d(TAG, "configureRegularScanParams() - queue emtpy, scan stopped");
                 }
             }
+
+            /* Check whether scan mode for LE Coded PHY has changed and compute the appropriate
+             * scan interval and scan window values
+             */
+            if((curScanSettingLECoded != Integer.MIN_VALUE
+                    && curScanSettingLECoded != ScanSettings.SCAN_MODE_OPPORTUNISTIC)) {
+                if ((curScanSettingLECoded != mLastConfiguredScanSettingLECoded) ||
+                       (curScanSettingLE1M != mLastConfiguredScanSettingLE1M)) {
+                    scanModeChanged = true;
+                    ScanSettings settings = new ScanSettings.Builder().setScanMode(curScanSettingLECoded).build();
+                    scanWindowLECoded = getScanWindowMillis(settings);
+                    scanIntervalLECoded = getScanIntervalMillis(settings);
+                    // convert scanWindow and scanInterval from ms to LE scan units(0.625ms)
+                    scanWindow[phyCnt] = Utils.millsToUnit(scanWindowLECoded);
+                    scanInterval[phyCnt] = Utils.millsToUnit(scanIntervalLECoded);
+                }
+            }
+            else {
+                mLastConfiguredScanSettingLECoded = curScanSettingLECoded;
+                if (DBG) {
+                    Log.d(TAG, "configureRegularScanParams() - queue emtpy, scan stopped");
+                }
+            }
+            if(scanModeChanged) {
+                gattClientScanNative(false);
+                if (DBG) {
+                    Log.d(TAG, "configureRegularScanParams - scanInterval LE 1M = " + scanIntervalLE1M
+                            + "configureRegularScanParams - scanWindow LE 1M= " + scanWindowLE1M
+                            + "configureRegularScanParams - scanInterval LE Coded= " + scanIntervalLECoded
+                            + "configureRegularScanParams - scanWindow LE Coded= " + scanWindowLECoded);
+                }
+                gattSetScanParametersNative(client.scannerId, scanPhy, scanInterval, scanWindow);
+                gattClientScanNative(true);
+                mLastConfiguredScanSettingLE1M = curScanSettingLE1M;
+                mLastConfiguredScanSettingLECoded = curScanSettingLECoded;
+            }
         }
 
         ScanClient getAggressiveClient(Set<ScanClient> cList) {
@@ -626,11 +759,46 @@
             return result;
         }
 
-        void startRegularScan(ScanClient client) {
+        PhyInfo getPhyInfo(Set<ScanClient> cList) {
+            PhyInfo result = null;
+            int curScanSettingLE1M = Integer.MIN_VALUE;
+            int curScanSettingLECoded = Integer.MIN_VALUE;
+            int curScanPhy = BluetoothDevice.PHY_LE_1M;
+            int aggregateScanPhy = BluetoothDevice.PHY_LE_1M;
+            for (ScanClient client : cList) {
+                // Get the most aggresive scan mode for each PHY
+                curScanPhy = client.settings.getPhy();
+                if (((curScanPhy & BluetoothDevice.PHY_LE_1M)== BluetoothDevice.PHY_LE_1M) &&
+                        (client.settings.getScanMode() > curScanSettingLE1M)) {
+                    curScanSettingLE1M = client.settings.getScanMode();
+                }
+                if (((curScanPhy & BluetoothDevice.PHY_LE_CODED)== BluetoothDevice.PHY_LE_CODED) &&
+                        (client.settings.getScanMode() > curScanSettingLECoded)) {
+                    curScanSettingLECoded = client.settings.getScanMode();
+                }
+                aggregateScanPhy |= client.settings.getPhy();
+            }
+            result = new PhyInfo(aggregateScanPhy, curScanSettingLE1M, curScanSettingLECoded);
+            return result;
+        }
+
+        boolean startRegularScan(ScanClient client) {
             if (isFilteringSupported() && mFilterIndexStack.isEmpty()
                     && mClientFilterIndexMap.isEmpty()) {
                 initFilterIndexStack();
             }
+
+            if (isRoutingScanClient(client) && (client.filters.size() > mFilterIndexStack.size())) {
+                try {
+                    mService.onScanManagerErrorCallback(client.scannerId,
+                                ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES);
+                    Log.e(TAG, "startRegularScan, routing scan out of HARDWARE_RESOURCES");
+                } catch (RemoteException e) {
+                    Log.e(TAG, "startRegularScan, routing scan failed on onScanManagerCallback", e);
+                }
+                return false;
+            }
+
             if (isFilteringSupported()) {
                 configureScanFilters(client);
             }
@@ -638,6 +806,7 @@
             if (numRegularScanClients() == 1) {
                 gattClientScanNative(true);
             }
+            return true;
         }
 
         private int numRegularScanClients() {
@@ -664,7 +833,7 @@
 
         private boolean isExemptFromScanDowngrade(ScanClient client) {
             return isOpportunisticScanClient(client) || isFirstMatchScanClient(client)
-                    || !shouldUseAllPassFilter(client);
+                    || !shouldUseAllPassFilter(client) || isRoutingScanClient(client);
         }
 
         private boolean isOpportunisticScanClient(ScanClient client) {
@@ -676,6 +845,10 @@
                     != 0;
         }
 
+        private boolean isRoutingScanClient(ScanClient client) {
+            return client.settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_SENSOR_ROUTING;
+        }
+
         private void resetBatchScan(ScanClient client) {
             int scannerId = client.scannerId;
             BatchScanParams batchScanParams = getBatchScanParams();
@@ -948,12 +1121,15 @@
                 for (ScanFilter filter : client.filters) {
                     ScanFilterQueue queue = new ScanFilterQueue();
                     queue.addScanFilter(filter);
+                    ScanFilterQueue.Entry[] entries = queue.toArray();
                     int featureSelection = queue.getFeatureSelection();
                     int filterIndex = mFilterIndexStack.pop();
 
-                    resetCountDownLatch();
-                    gattClientScanFilterAddNative(scannerId, queue.toArray(), filterIndex);
-                    waitForCallback();
+                    if (entries != null && entries.length > 0) {
+                        resetCountDownLatch();
+                        gattClientScanFilterAddNative(scannerId, entries, filterIndex);
+                        waitForCallback();
+                    }
 
                     resetCountDownLatch();
                     if (deliveryMode == DELIVERY_MODE_ON_FOUND_LOST) {
@@ -1055,6 +1231,9 @@
             if (client == null) {
                 return true;
             }
+            if (isRoutingScanClient(client)) {
+                return false;
+            }
             if (client.filters == null || client.filters.isEmpty()) {
                 return true;
             }
@@ -1085,7 +1264,7 @@
             onLostTimeout = 10000;
             if (DBG) {
                 Log.d(TAG, "configureFilterParamter " + onFoundTimeout + " " + onLostTimeout + " "
-                        + onFoundCount + " " + numOfTrackingEntries);
+                        + onFoundCount + " " + numOfTrackingEntries + ", deliveryMode=" + deliveryMode);
             }
             FilterParams filtValue =
                     new FilterParams(scannerId, filterIndex, featureSelection, LIST_LOGIC_TYPE,
@@ -1107,6 +1286,9 @@
                     || (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) {
                 return DELIVERY_MODE_ON_FOUND_LOST;
             }
+            if (isRoutingScanClient(client)) {
+                return DELIVERY_MODE_ROUTE;
+            }
             return settings.getReportDelayMillis() == 0 ? DELIVERY_MODE_IMMEDIATE
                     : DELIVERY_MODE_BATCH;
         }
@@ -1266,8 +1448,8 @@
 
         private native void gattClientScanNative(boolean start);
 
-        private native void gattSetScanParametersNative(int clientIf, int scanInterval,
-                int scanWindow);
+        private native void gattSetScanParametersNative(int clientIf, int scan_phy, int[] scanInterval,
+                                                        int[] scanWindow);
 
         /************************** Filter related native methods ********************************/
         private native void gattClientScanFilterAddNative(int clientId,
@@ -1313,6 +1495,23 @@
         return false;
     }
 
+    public boolean isAptXLowLatencyModeEnabled() {
+        Log.d(TAG, "isAptXLowLatencyModeEnabled: " + mIsAptXLowLatencyModeEnabled);
+        return mIsAptXLowLatencyModeEnabled;
+    }
+
+    public void setAptXLowLatencyMode(boolean enabled){
+        Log.d(TAG, "setAptXLowLatencyMode: mIsAptXLowLatencyModeEnabled: "
+            + mIsAptXLowLatencyModeEnabled + "enabled: " + enabled);
+        mIsAptXLowLatencyModeEnabled = enabled;
+        if (mIsAptXLowLatencyModeEnabled) {
+            sendMessage(MSG_SUSPEND_SCAN_ALL, null);
+        } else {
+            sendMessage(MSG_RESUME_SCANS, null);
+        }
+    }
+
+
     private final DisplayManager.DisplayListener mDisplayListener =
             new DisplayManager.DisplayListener() {
                 @Override
@@ -1323,11 +1522,7 @@
 
                 @Override
                 public void onDisplayChanged(int displayId) {
-                    if (isScreenOn() && mLocationManager.isLocationEnabled()) {
-                        sendMessage(MSG_RESUME_SCANS, null);
-                    } else {
-                        sendMessage(MSG_SUSPEND_SCANS, null);
-                    }
+                    sendMessage(MSG_HANDLE_DISPLAY_CHANGED, null);
                 }
             };
 
@@ -1400,4 +1595,12 @@
             mScanNative.configureRegularScanParams();
         }
     }
+
+    private void handleScanOnDisplayChanged() {
+        if (isScreenOn() && mLocationManager.isLocationEnabled()) {
+            sendMessage(MSG_RESUME_SCANS, null);
+        } else {
+            sendMessage(MSG_SUSPEND_SCANS, null);
+        }
+    }
 }
diff --git a/src/com/android/bluetooth/hdp/HealthService.java b/src/com/android/bluetooth/hdp/HealthService.java
index 6a4af04..b60b0d6 100644
--- a/src/com/android/bluetooth/hdp/HealthService.java
+++ b/src/com/android/bluetooth/hdp/HealthService.java
@@ -221,8 +221,13 @@
                 break;
                 case MESSAGE_UNREGISTER_APPLICATION: {
                     BluetoothHealthAppConfiguration appConfig =
-                            (BluetoothHealthAppConfiguration) msg.obj;
-                    int appId = (mApps.get(appConfig)).mAppId;
+                        (BluetoothHealthAppConfiguration) msg.obj;
+                    AppInfo appInfo = mApps.get(appConfig);
+                    if (appInfo == null) {
+                        Log.e(TAG, "No AppInfo found for AppConfig: " + appConfig);
+                        break;
+                    }
+                    int appId = appInfo.mAppId;
                     if (!unregisterHealthAppNative(appId)) {
                         Log.e(TAG, "Failed to unregister application: id: " + appId);
                         callStatusCallback(appConfig,
@@ -233,7 +238,12 @@
                 case MESSAGE_CONNECT_CHANNEL: {
                     HealthChannel chan = (HealthChannel) msg.obj;
                     byte[] devAddr = Utils.getByteAddress(chan.mDevice);
-                    int appId = (mApps.get(chan.mConfig)).mAppId;
+                    AppInfo appInfo = mApps.get(chan.mConfig);
+                    if (appInfo == null) {
+                        Log.e(TAG, "No AppInfo found for AppConfig: " + chan.mConfig);
+                        break;
+                    }
+                    int appId = appInfo.mAppId;
                     chan.mChannelId = connectChannelNative(devAddr, appId);
                     if (chan.mChannelId == -1) {
                         callHealthChannelCallback(chan.mConfig, chan.mDevice,
@@ -273,6 +283,10 @@
                             || regStatus == BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS) {
                         //unlink to death once app is unregistered
                         AppInfo appInfo = mApps.get(appConfig);
+                        if (appInfo == null){
+                            Log.e(TAG, "No AppInfo found for AppConfig " + appConfig);
+                            break;
+                        }
                         appInfo.cleanup();
                         mApps.remove(appConfig);
                     }
@@ -285,9 +299,9 @@
                             findAppConfigByAppId(channelStateEvent.mAppId);
                     int newState;
                     newState = convertHalChannelState(channelStateEvent.mState);
-                    if (newState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED
-                            && appConfig == null) {
-                        Log.e(TAG, "Disconnected for non existing app");
+                    if (newState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED ||
+                        appConfig == null) {
+                        Log.e(TAG,"Disconnected for non existing app");
                         break;
                     }
                     if (chan == null) {
@@ -588,9 +602,15 @@
         if (VDBG) {
             Log.d(TAG, "Health Device Application: " + config + " State Change: status:" + status);
         }
-        IBluetoothHealthCallback callback = (mApps.get(config)).mCallback;
+        AppInfo appInfo = mApps.get(config);
+        if (appInfo == null) {
+            Log.e(TAG, " No AppInfo found for AppConfig " + config);
+            return;
+        }
+        IBluetoothHealthCallback callback = appInfo.mCallback;
         if (callback == null) {
             Log.e(TAG, "Callback object null");
+            return;
         }
 
         try {
@@ -680,8 +700,12 @@
                 Log.e(TAG, "Exception while duping: " + e);
             }
         }
-
-        IBluetoothHealthCallback callback = (mApps.get(config)).mCallback;
+        AppInfo appInfo = mApps.get(config);
+        if (appInfo == null) {
+            Log.e(TAG, "No AppInfo found for AppConfig " + config);
+            return;
+        }
+        IBluetoothHealthCallback callback = appInfo.mCallback;
         if (callback == null) {
             Log.e(TAG, "No callback found for config: " + config);
             return;
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidService.java b/src/com/android/bluetooth/hearingaid/HearingAidService.java
index b30eb62..d1c99be 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidService.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -40,6 +40,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -764,6 +765,7 @@
             int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
             int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
             connectionStateChanged(device, fromState, toState);
+
         }
     }
 
diff --git a/src/com/android/bluetooth/hfp/AtPhonebook.java b/src/com/android/bluetooth/hfp/AtPhonebook.java
index 937e767..6e4650d 100644
--- a/src/com/android/bluetooth/hfp/AtPhonebook.java
+++ b/src/com/android/bluetooth/hfp/AtPhonebook.java
@@ -23,8 +23,10 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.CommonDataKinds;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.Contacts;
 import android.telephony.PhoneNumberUtils;
 import android.util.Log;
 
@@ -62,9 +64,27 @@
      *  BT periphals don't. Limit the number we'll report. */
     private static final int MAX_PHONEBOOK_SIZE = 16384;
 
+    private final String SIM_URI = "content://icc/adn";
+
+    static final String[] SIM_PROJECTION = new String[] {
+            Contacts.DISPLAY_NAME,
+            CommonDataKinds.Phone.NUMBER,
+            CommonDataKinds.Phone.TYPE,
+            CommonDataKinds.Phone.LABEL
+    };
+
+    private static final int NAME_COLUMN_INDEX = 0;
+    private static final int NUMBER_COLUMN_INDEX = 1;
+    private static final int NUMBERTYPE_COLUMN_INDEX = 2;
+
     private static final String OUTGOING_CALL_WHERE = Calls.TYPE + "=" + Calls.OUTGOING_TYPE;
     private static final String INCOMING_CALL_WHERE = Calls.TYPE + "=" + Calls.INCOMING_TYPE;
     private static final String MISSED_CALL_WHERE = Calls.TYPE + "=" + Calls.MISSED_TYPE;
+    private static final String VISIBLE_PHONEBOOK_WHERE = null;
+    private static final String VISIBLE_SIM_PHONEBOOK_WHERE = null;
+
+    public static final int OUTGOING_IMS_TYPE = 1001;
+    public static final int OUTGOING_WIFI_TYPE = 1004;
 
     private class PhonebookResult {
         public Cursor cursor; // result set of last query
@@ -88,7 +108,7 @@
     private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
 
     private final HashMap<String, PhonebookResult> mPhonebooks =
-            new HashMap<String, PhonebookResult>(4);
+            new HashMap<String, PhonebookResult>(5);
 
     static final int TYPE_UNKNOWN = -1;
     static final int TYPE_READ = 0;
@@ -104,6 +124,8 @@
         mPhonebooks.put("RC", new PhonebookResult());  // received calls
         mPhonebooks.put("MC", new PhonebookResult());  // missed calls
         mPhonebooks.put("ME", new PhonebookResult());  // mobile phonebook
+        mPhonebooks.put("SM", new PhonebookResult());  // SIM phonebook
+
         mCurrentPhonebook = "ME";  // default to mobile phonebook
         mCpbrIndex1 = mCpbrIndex2 = -1;
     }
@@ -115,24 +137,32 @@
     /** Returns the last dialled number, or null if no numbers have been called */
     public String getLastDialledNumber() {
         String[] projection = {Calls.NUMBER};
-        Cursor cursor = mContentResolver.query(Calls.CONTENT_URI, projection,
-                Calls.TYPE + "=" + Calls.OUTGOING_TYPE, null,
-                Calls.DEFAULT_SORT_ORDER + " LIMIT 1");
-        if (cursor == null) {
-            Log.w(TAG, "getLastDialledNumber, cursor is null");
-            return null;
-        }
+        try {
+            Cursor cursor = mContentResolver.query(Calls.CONTENT_URI, projection,
+                    Calls.TYPE + " = " + Calls.OUTGOING_TYPE + " OR " + Calls.TYPE +
+                    " = " + OUTGOING_IMS_TYPE + " OR " + Calls.TYPE + " = " +
+                    OUTGOING_WIFI_TYPE, null, Calls.DEFAULT_SORT_ORDER +
+                    " LIMIT 1");
+            log("Queried the last dialled number for CS, IMS, WIFI calls");
+            if (cursor == null) {
+                Log.w(TAG, "getLastDialledNumber, cursor is null");
+                return null;
+            }
 
-        if (cursor.getCount() < 1) {
+            if (cursor.getCount() < 1) {
+                cursor.close();
+                Log.w(TAG, "getLastDialledNumber, cursor.getCount is 0");
+                return null;
+            }
+            cursor.moveToNext();
+            int column = cursor.getColumnIndexOrThrow(Calls.NUMBER);
+            String number = cursor.getString(column);
             cursor.close();
-            Log.w(TAG, "getLastDialledNumber, cursor.getCount is 0");
-            return null;
+            return number;
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while querying last dialled number", e);
         }
-        cursor.moveToNext();
-        int column = cursor.getColumnIndexOrThrow(Calls.NUMBER);
-        String number = cursor.getString(column);
-        cursor.close();
-        return number;
+        return null;
     }
 
     public boolean getCheckingAccessPermission() {
@@ -206,11 +236,6 @@
             case TYPE_READ: // Read
                 log("handleCpbsCommand - read command");
                 // Return current size and max size
-                if ("SM".equals(mCurrentPhonebook)) {
-                    atCommandResponse = "+CPBS: \"SM\",0," + getMaxPhoneBookSize(0);
-                    atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
-                    break;
-                }
                 PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true);
                 if (pbr == null) {
                     atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_SUPPORTED;
@@ -279,21 +304,17 @@
                  */
                 log("handleCpbrCommand - test command");
                 int size;
-                if ("SM".equals(mCurrentPhonebook)) {
-                    size = 0;
-                } else {
-                    PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true); //false);
-                    if (pbr == null) {
-                        atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
-                        mNativeInterface.atResponseCode(remoteDevice, atCommandResult,
-                                atCommandErrorCode);
-                        break;
-                    }
-                    size = pbr.cursor.getCount();
-                    log("handleCpbrCommand - size = " + size);
-                    pbr.cursor.close();
-                    pbr.cursor = null;
+                PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true); //false);
+                if (pbr == null) {
+                    atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
+                    mNativeInterface.atResponseCode(remoteDevice, atCommandResult,
+                            atCommandErrorCode);
+                    break;
                 }
+                size = pbr.cursor.getCount();
+                log("handleCpbrCommand - size = "+size);
+                pbr.cursor.close();
+                pbr.cursor = null;
                 if (size == 0) {
                     /* Sending "+CPBR: (1-0)" can confused some carkits, send "1-1" * instead */
                     size = 1;
@@ -310,6 +331,7 @@
                 // AT+CPBR=<index1>[,<index2>]
                 log("handleCpbrCommand - set/read command");
                 if (mCpbrIndex1 != -1) {
+                   Log.i(TAG, "mCpbrIndex1 :" + mCpbrIndex1);
                    /* handling a CPBR at the moment, reject this CPBR command */
                     atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
                     mNativeInterface.atResponseCode(remoteDevice, atCommandResult,
@@ -349,7 +371,9 @@
                 mCheckingAccessPermission = true;
 
                 int permission = checkAccessPermission(remoteDevice);
+                Log.i(TAG, "permission :" + permission);
                 if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+                    Log.i(TAG, "permission to access is granted:" );
                     mCheckingAccessPermission = false;
                     atCommandResult = processCpbrCommand(remoteDevice);
                     mCpbrIndex1 = mCpbrIndex2 = -1;
@@ -357,6 +381,7 @@
                             atCommandErrorCode);
                     break;
                 } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
+                    Log.i(TAG, "permission to access is not granted:" );
                     mCheckingAccessPermission = false;
                     mCpbrIndex1 = mCpbrIndex2 = -1;
                     mNativeInterface.atResponseCode(remoteDevice,
@@ -400,6 +425,7 @@
     private synchronized boolean queryPhonebook(String pb, PhonebookResult pbr) {
         String where;
         boolean ancillaryPhonebook = true;
+        boolean simPhonebook = false;
 
         if (pb.equals("ME")) {
             ancillaryPhonebook = false;
@@ -410,6 +436,10 @@
             where = INCOMING_CALL_WHERE;
         } else if (pb.equals("MC")) {
             where = MISSED_CALL_WHERE;
+        } else if (pb.equals("SM")) {
+            ancillaryPhonebook = false;
+            simPhonebook = true;
+            where = VISIBLE_SIM_PHONEBOOK_WHERE;
         } else {
             return false;
         }
@@ -420,9 +450,14 @@
         }
 
         if (ancillaryPhonebook) {
-            pbr.cursor = mContentResolver.query(Calls.CONTENT_URI, CALLS_PROJECTION, where, null,
-                    Calls.DEFAULT_SORT_ORDER + " LIMIT " + MAX_PHONEBOOK_SIZE);
-            if (pbr.cursor == null) {
+            try {
+                pbr.cursor = mContentResolver.query(Calls.CONTENT_URI, CALLS_PROJECTION, where,
+                          null, Calls.DEFAULT_SORT_ORDER + " LIMIT " + MAX_PHONEBOOK_SIZE);
+                if (pbr.cursor == null) {
+                    return false;
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Exception while querying the call log database", e);
                 return false;
             }
 
@@ -432,17 +467,56 @@
             pbr.typeColumn = -1;
             pbr.nameColumn = -1;
         } else {
-            final Uri phoneContentUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
-            pbr.cursor = mContentResolver.query(phoneContentUri, PHONES_PROJECTION, where, null,
-                    Phone.NUMBER + " LIMIT " + MAX_PHONEBOOK_SIZE);
-            if (pbr.cursor == null) {
-                return false;
-            }
+            Log.i(TAG, "simPhonebook " + simPhonebook);
+            if (simPhonebook) {
+                final Uri mysimUri = Uri.parse(SIM_URI);
+                try {
+                    pbr.cursor = mContentResolver.query(mysimUri, SIM_PROJECTION,
+                            where, null, null);
+                    Log.i(TAG, "querySIMcontactbook where " + where + " uri :" + mysimUri);
+                    if (pbr.cursor == null) {
+                        Log.i(TAG, "querying phone contacts on sim returned null.");
+                        return false;
+                    }
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception while querying sim contact book database", e);
+                    return false;
+                }
 
-            pbr.numberColumn = pbr.cursor.getColumnIndex(Phone.NUMBER);
-            pbr.numberPresentationColumn = -1;
-            pbr.typeColumn = pbr.cursor.getColumnIndex(Phone.TYPE);
-            pbr.nameColumn = pbr.cursor.getColumnIndex(Phone.DISPLAY_NAME);
+                pbr.numberColumn = NUMBER_COLUMN_INDEX;
+                pbr.numberPresentationColumn = -1;
+                pbr.typeColumn = NUMBERTYPE_COLUMN_INDEX;
+                pbr.nameColumn = NAME_COLUMN_INDEX;
+                Log.i(TAG, " pbr.numberColumn: " + pbr.numberColumn +
+                           " pbr.numberPresentationColumn: " + pbr.numberPresentationColumn +
+                           " pbr.typeColumn: " + pbr.typeColumn +
+                           " pbr.nameColumn: " + pbr.nameColumn);
+            } else {
+                final Uri phoneContentUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
+                try {
+                    pbr.cursor = mContentResolver.query(phoneContentUri, PHONES_PROJECTION,
+                            where, null, Phone.NUMBER + " LIMIT " + MAX_PHONEBOOK_SIZE);
+                    Log.i(TAG, "queryPhonebook where " + where + " uri :" + phoneContentUri);
+                    if (pbr.cursor == null) {
+                        Log.i(TAG, "querying phone contacts on memory returned null.");
+                        return false;
+                    }
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception while querying phone contacts on memory", e);
+                    return false;
+                }
+
+                Log.i(TAG, "Phone.NUMBER: " + Phone.NUMBER + " Phone.TYPE :" + Phone.TYPE +
+                              "Phone.DISPLAY_NAME :" + Phone.DISPLAY_NAME);
+                pbr.numberColumn = pbr.cursor.getColumnIndex(Phone.NUMBER);
+                pbr.numberPresentationColumn = -1;
+                pbr.typeColumn = pbr.cursor.getColumnIndex(Phone.TYPE);
+                pbr.nameColumn = pbr.cursor.getColumnIndex(Phone.DISPLAY_NAME);
+                Log.i(TAG, " pbr.numberColumn: " + pbr.numberColumn +
+                           " pbr.numberPresentationColumn: " + pbr.numberPresentationColumn +
+                           " pbr.typeColumn: " + pbr.typeColumn +
+                           " pbr.nameColumn: " + pbr.nameColumn);
+            }
         }
         Log.i(TAG, "Refreshed phonebook " + pb + " with " + pbr.cursor.getCount() + " results");
         return true;
@@ -485,12 +559,6 @@
         StringBuilder response = new StringBuilder();
         String record;
 
-        // Shortcut SM phonebook
-        if ("SM".equals(mCurrentPhonebook)) {
-            atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
-            return atCommandResult;
-        }
-
         // Check phonebook
         PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true); //false);
         if (pbr == null) {
@@ -528,18 +596,24 @@
                 // try caller id lookup
                 // TODO: This code is horribly inefficient. I saw it
                 // take 7 seconds to process 100 missed calls.
-                Cursor c = mContentResolver.query(
-                        Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, number),
-                        new String[]{
+                try {
+                    Cursor c = mContentResolver.query(
+                            Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
+                            number), new String[]{
                                 PhoneLookup.DISPLAY_NAME, PhoneLookup.TYPE
-                        }, null, null, null);
-                if (c != null) {
-                    if (c.moveToFirst()) {
-                        name = c.getString(0);
-                        type = c.getInt(1);
+                            }, null, null, null);
+                    if (c != null) {
+                        if (c.moveToFirst()) {
+                            name = c.getString(0);
+                            type = c.getInt(1);
+                        }
+                        c.close();
                     }
-                    c.close();
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception while querying phonebook database", e);
+                    return HeadsetHalConstants.AT_RESPONSE_ERROR;
                 }
+
                 if (DBG && name == null) {
                     log("Caller ID lookup failed for " + number);
                 }
diff --git a/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java b/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java
new file mode 100644
index 0000000..1b46dd7
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java
@@ -0,0 +1,246 @@
+/*
+ *  Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *      * Redistributions of source code must retain the above copyright
+ *        notice, this list of conditions and the following disclaimer.
+ *      * Redistributions in binary form must reproduce the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer in the documentation and/or other materials provided
+ *        with the distribution.
+ *      * Neither the name of The Linux Foundation nor the names of its
+ *        contributors may be used to endorse or promote products derived
+ *        from this software without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ *  ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ *  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ *  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ *  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.bluetooth.hfp;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Defines methods used for synchronization between HFP and A2DP
+ */
+public class HeadsetA2dpSync {
+    private static final String TAG = HeadsetA2dpSync.class.getSimpleName();
+
+    // system inteface
+    private HeadsetSystemInterface mSystemInterface;
+    private HeadsetService mHeadsetService;
+    // Hash for storing the A2DP states
+    private ConcurrentHashMap<BluetoothDevice, Integer> mA2dpConnState =
+                            new ConcurrentHashMap<BluetoothDevice, Integer>();
+
+    // internal variables.
+    private int mA2dpSuspendTriggered;// to keep track if A2dp was supended by HFP.
+    private BluetoothDevice mDummyDevice = null;
+
+    //reason for a2dp suspended.
+    public static final int A2DP_SUSPENDED_NOT_TRIGGERED = 0;
+    public static final int A2DP_SUSPENDED_BY_CS_CALL = 1;
+    public static final int A2DP_SUSPENDED_BY_VOIP_CALL = 2;
+    public static final int A2DP_SUSPENDED_BY_VR = 3;
+    // State for a2dp Device
+    // current implementation is only concerned about DISCONNECTED, CONNECTED and PLAYING.
+    public static final int A2DP_DISCONNECTED = 0;// this implies no connection
+    public static final int A2DP_CONNECTING = 1;
+    public static final int A2DP_CONNECTED = 2;// this implies connected but not playing
+    public static final int A2DP_DISCONNECTING = 3;
+    public static final int A2DP_PLAYING = 4;// this implies connected and PLaying
+    public static final int A2DP_SUSPENDED = 5;
+
+    HeadsetA2dpSync(HeadsetSystemInterface systemInterface,HeadsetService service) {
+        mSystemInterface = systemInterface;
+        mHeadsetService = service;
+        mA2dpSuspendTriggered = A2DP_SUSPENDED_NOT_TRIGGERED;// initialize with not suspended.
+        String dummyAddress = "AA:BB:CC:DD:EE:00";
+        mDummyDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(dummyAddress);
+    }
+
+    /* check if any of the a2dp device is playing
+     * TODO: Remove if not required.
+     */
+    /* check if any device is in PLAYING/CONNECTED state
+     * return types:
+     * A2DP_DISCONNECTED: NO Device COnnected
+     * A2DP_CONNECTED: All Devices are in Connected state, none in playing state
+     * A2DP_PLAYING: Atlease one device in playing state
+     */
+    public int isA2dpPlaying() {
+        int a2dpState = A2DP_DISCONNECTED;
+        for(Integer value: mA2dpConnState.values()) {
+            if(value == A2DP_CONNECTED) {
+                a2dpState = value;
+            }
+            if(value == A2DP_PLAYING) {
+                a2dpState = value;
+                Log.d(TAG," isA2dpPlaying returns = " + a2dpState);
+                return a2dpState;
+            }
+        }
+        Log.d(TAG," isA2dpPlaying returns = " + a2dpState);
+        return a2dpState;
+    }
+    /* check and suspend A2DP
+     * return: true in case we have to wait for suepnd confirmation
+     *         false in case we don't have to wait for suepnd confirmation
+     * If A2DP is connected, call A2dpSuspended=true ( so that A2DP can't start while call
+     * is acvtive), but we don't have to wait for suspend confirmation in this case
+     * If A2DP is playing, call A2dpSuspended=true, and wait for suspend confirm
+     * if A2DP is not connected, don't do anything.
+     * caller need to send reason for suspend request( VR/CS-CALL/VOIP-CALL)
+     */
+    public boolean suspendA2DP(int reason, BluetoothDevice device){
+        int a2dpState = isA2dpPlaying();
+        Log.d(TAG," suspendA2DP currPlayingState = "+ a2dpState + " for reason " + reason
+              + "mA2dpSuspendTriggered = " + mA2dpSuspendTriggered + " for device " + device);
+        if (mA2dpSuspendTriggered != A2DP_SUSPENDED_NOT_TRIGGERED) {
+            // A2DPSuspend was triggered already, don't need to do anything.
+            if(a2dpState == A2DP_PLAYING) {
+                // we are still waiting for suspend from a2dp.Caller shld wait
+                return true;
+            } else {
+                // not playing, caller need not wait.
+                return false;
+            }
+        }
+        // not device in connected state, nothing to do. Caller shld not wait
+        if(a2dpState == A2DP_DISCONNECTED) {
+            Log.d(TAG," A2DP not Connected, nothing to do ");
+            return false;
+        }
+        if(a2dpState == A2DP_CONNECTED) {
+            mA2dpSuspendTriggered = reason;
+            mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
+            Log.d(TAG," A2DP Connected,don't wait for suspend ");
+            return false;
+        }
+        if(a2dpState == A2DP_PLAYING) {
+            mA2dpSuspendTriggered = reason;
+            mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
+            Log.d(TAG," A2DP Playing ,wait for suspend ");
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     *This api will be called by SMs, to make A2dpSuspended=false
+     */
+    public boolean releaseA2DP(BluetoothDevice device) {
+        Log.d(TAG," releaseA2DP mA2dpSuspendTriggered " + mA2dpSuspendTriggered +
+                            " by device " + device);
+        if(mA2dpSuspendTriggered == A2DP_SUSPENDED_NOT_TRIGGERED) {
+            return true;
+        }
+        if (mHeadsetService.isInCall() || mHeadsetService.isRinging() ||
+                                                 mHeadsetService.isAudioOn()) {
+            Log.d(TAG," Call/Ring/SCO on for some other stateMachine, bail out ");
+            return true;
+        }
+        // A2DP Suspend was triggered by HFP.
+        mA2dpSuspendTriggered = A2DP_SUSPENDED_NOT_TRIGGERED;
+        mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
+        return true;
+    }
+
+    public void updateA2DPPlayingState(Intent intent) {
+        int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
+                                       BluetoothA2dp.STATE_NOT_PLAYING);
+        int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+                                       BluetoothA2dp.STATE_NOT_PLAYING);
+        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+        Log.d(TAG," updateA2DPPlayingState device: " + device + " transition " +
+                                             prevState + "->" + currState);
+
+        // if device is not there and  state=DISCONNECTED, bail out
+        if(!mA2dpConnState.containsKey(device)) {
+            Log.e(TAG," Got PLay_UPdate without Connectoin Update, this shld not happen "+ device);
+        }
+        switch(currState) {
+        case BluetoothA2dp.STATE_NOT_PLAYING:
+            mA2dpConnState.put(device, A2DP_CONNECTED);
+            /*
+             * send message to statemachine. We send message to SMs
+             * only when all devices moved to SUSPENDED.
+             */
+            int a2dpState = isA2dpPlaying();
+            if ((a2dpState == A2DP_DISCONNECTED) ||
+                       (a2dpState == A2DP_CONNECTED)) {
+                mHeadsetService.sendA2dpStateChangeUpdate(a2dpState);
+            }
+            break;
+        case BluetoothA2dp.STATE_PLAYING:
+            mA2dpConnState.put(device, A2DP_PLAYING);
+            // if call/ ring is ongoing and there is HFP connected device and we received playing,
+            // we need to suspend
+            if ((mHeadsetService.isInCall() || mHeadsetService.isRinging()) &&
+                 mHeadsetService.getConnectedDevices().size() != 0) {
+                Log.d(TAG," CALL/Ring is active ");
+                suspendA2DP(A2DP_SUSPENDED_BY_CS_CALL, mDummyDevice);
+            }
+            break;
+        }
+        Log.d(TAG," device: " + device + " state = " + mA2dpConnState.get(device));
+    }
+    /*
+     * BookKeeping of A2dp Device State, based on intents from A2DPStateMachine.
+     */
+    public void updateA2DPConnectionState(Intent intent) {
+        int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
+                                       BluetoothProfile.STATE_DISCONNECTED);
+        int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+                                       BluetoothProfile.STATE_DISCONNECTED);
+        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+        Log.d(TAG," updateA2DPConnectionState device: " + device + " transition " +
+                                             prevState + "->" + currState);
+
+        // if device is not there and  state=DISCONNECTED, bail out
+        if(!mA2dpConnState.containsKey(device) &&
+           (currState == BluetoothProfile.STATE_DISCONNECTED)) {
+            Log.e(TAG," Got Disc for device not in entry "+ device);
+                return;
+        }
+        switch(currState) {
+        case BluetoothProfile.STATE_DISCONNECTED:
+            mA2dpConnState.remove(device);
+            break;
+        // treating everything else as Connected
+        case BluetoothProfile.STATE_DISCONNECTING:
+        case BluetoothProfile.STATE_CONNECTING:
+            mA2dpConnState.put(device, A2DP_CONNECTED);
+            break;
+        case BluetoothProfile.STATE_CONNECTED:
+            mA2dpConnState.put(device, A2DP_CONNECTED);
+            if (mHeadsetService.isInCall() || mHeadsetService.isRinging()) {
+                Log.d(TAG," CALL is active/ringing, A2DP got connected, suspending");
+                suspendA2DP(A2DP_SUSPENDED_BY_CS_CALL, mDummyDevice);
+            }
+            break;
+        }
+        Log.d(TAG," device: " + device + " state = " + mA2dpConnState.get(device));
+    }
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index bcf123c..93012e2 100644
--- a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -33,11 +33,11 @@
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.PhoneConstants;
 
 import java.util.HashMap;
 import java.util.Objects;
 
-
 /**
  * Class that manages Telephony states
  *
@@ -72,6 +72,18 @@
     private int mCindRoam = HeadsetHalConstants.SERVICE_TYPE_HOME;
     // HFP 1.6 CIND battchg value
     private int mCindBatteryCharge;
+    // Current Call Number
+    private String mCindNumber;
+    //Current Phone Number Type
+    private int mType = 0;
+    // if its a CS call
+    private boolean mIsCsCall = true;
+    // SIM is absent
+    private int SIM_ABSENT = 0;
+    // SIM is present
+    private int SIM_PRESENT = 1;
+    // Array to keep the SIM status
+    private int[] mSimStatus = {0, 0};
 
     private final HashMap<BluetoothDevice, Integer> mDeviceEventMap = new HashMap<>();
     private PhoneStateListener mPhoneStateListener;
@@ -91,6 +103,25 @@
         mOnSubscriptionsChangedListener = new HeadsetPhoneStateOnSubscriptionChangedListener(
                 headsetService.getStateMachinesThreadLooper());
         mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+        IntentFilter simStateChangedFilter =
+                        new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        //Record the SIM states upon BT reset
+        try {
+            for (int slotIndex = 0; slotIndex < mSimStatus.length; slotIndex++) {
+                if (mTelephonyManager.getSimState(slotIndex) ==
+                        TelephonyManager.SIM_STATE_READY) {
+                    Log.d(TAG, "The sim in slotIndex: " + slotIndex + " is present");
+                    mSimStatus[slotIndex] = SIM_PRESENT;
+                } else {
+                    Log.d(TAG, "The sim in slotIndex: " + slotIndex + " is absent");
+                    mSimStatus[slotIndex] = SIM_ABSENT;
+                }
+            }
+            mHeadsetService.registerReceiver(mPhoneStateChangeReceiver, simStateChangedFilter);
+        } catch (Exception e) {
+            Log.w(TAG, "Unable to register phone state change receiver and unable to get" +
+                       " sim states", e);
+        }
     }
 
     /**
@@ -102,6 +133,11 @@
             stopListenForPhoneState();
         }
         mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+        try {
+             mHeadsetService.unregisterReceiver(mPhoneStateChangeReceiver);
+        } catch (Exception e) {
+            Log.w(TAG, "Unable to unregister phone state change receiver", e);
+        }
     }
 
     @Override
@@ -160,13 +196,22 @@
             return;
         }
         Log.i(TAG, "startListenForPhoneState(), subId=" + subId + ", enabled_events=" + events);
-        mPhoneStateListener = new HeadsetPhoneStateListener(subId,
-                mHeadsetService.getStateMachinesThreadLooper());
-        mTelephonyManager.listen(mPhoneStateListener, events);
-        if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
-            mTelephonyManager.setRadioIndicationUpdateMode(
-                    TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
-                    TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
+        if (mTelephonyManager == null) {
+            Log.e(TAG, "mTelephonyManager is null, "
+                 + "cannot start listening for phone state changes");
+        } else {
+            mPhoneStateListener = new HeadsetPhoneStateListener(subId,
+                    mHeadsetService.getStateMachinesThreadLooper());
+            try {
+                mTelephonyManager.listen(mPhoneStateListener, events);
+                if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
+                    mTelephonyManager.setRadioIndicationUpdateMode(
+                            TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                            TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
+                }
+            } catch (Exception e) {
+                Log.w(TAG, "Exception while registering for signal strength notifications", e);
+            }
         }
     }
 
@@ -177,13 +222,61 @@
         }
         Log.i(TAG, "stopListenForPhoneState(), stopping listener, enabled_events="
                 + getTelephonyEventsToListen());
-        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
-        mTelephonyManager.setRadioIndicationUpdateMode(
-                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
-                TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
+        if (mTelephonyManager == null) {
+            Log.e(TAG, "mTelephonyManager is null, "
+                + "cannot send request to stop listening or update radio to normal state");
+        } else {
+            try {
+                mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+                mTelephonyManager.setRadioIndicationUpdateMode(
+                        TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                        TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
+            } catch (Exception e) {
+                Log.w(TAG, "exception while registering for signal strength notifications", e);
+            }
+        }
         mPhoneStateListener = null;
     }
 
+    private final BroadcastReceiver mPhoneStateChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.d(TAG, "onReceive: " + intent.getAction());
+            final String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
+            if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
+                // This is a sticky broadcast, so if it's already been loaded,
+                // this'll execute immediately.
+                if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(stateExtra)) {
+                    final int slotId = intent.getIntExtra(PhoneConstants.SLOT_KEY,
+                                       SubscriptionManager.getDefaultVoicePhoneId());
+                    Log.d(TAG, "SIM loaded, making mIsSimStateLoaded to true for slotId = "
+                               + slotId);
+                    mSimStatus[slotId] = SIM_PRESENT;
+                    mIsSimStateLoaded = true;
+                    sendDeviceStateChanged();
+                } else if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)
+                           || IccCardConstants.INTENT_VALUE_ICC_UNKNOWN.equals(stateExtra)
+                           || IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
+                    final int slotId = intent.getIntExtra(PhoneConstants.SLOT_KEY,
+                                       SubscriptionManager.getDefaultVoicePhoneId());
+                    Log.d(TAG, "SIM unloaded, making mIsSimStateLoaded to false for slotId = "
+                               + slotId);
+                    mSimStatus[slotId] = SIM_ABSENT;
+                    mIsSimStateLoaded = false;
+                    for (int i = 0; i < mSimStatus.length; i++) {
+                        if (mSimStatus[i] == SIM_PRESENT) {
+                            Log.d(TAG, "SIM is loaded in either of the slots, making" +
+                                  " mIsSimStateLoaded to true");
+                              mIsSimStateLoaded = true;
+                              break;
+                         }
+                    }
+                    sendDeviceStateChanged();
+                }
+            }
+        }
+    };
+
     int getCindService() {
         return mCindService;
     }
@@ -197,6 +290,16 @@
         mNumActive = numActive;
     }
 
+    boolean getIsCsCall() {
+        Log.d(TAG, "In getIsCsCall, mIsCsCall: " + mIsCsCall);
+        return mIsCsCall;
+    }
+
+    void setIsCsCall(boolean isCsCall) {
+        Log.d(TAG, "In setIsCsCall, mIsCsCall: " + isCsCall);
+        mIsCsCall = isCsCall;
+    }
+
     int getCallState() {
         return mCallState;
     }
@@ -219,6 +322,22 @@
         return mCindSignal;
     }
 
+    void setNumber(String mNumberCall ) {
+        mCindNumber = mNumberCall;
+    }
+
+    String getNumber() {
+        return mCindNumber;
+    }
+
+    void setType(int mTypeCall) {
+        mType = mTypeCall;
+    }
+
+    int getType() {
+        return mType;
+    }
+
     int getCindRoam() {
         return mCindRoam;
     }
@@ -251,7 +370,7 @@
         // use the service indicator, but only the signal indicator
         int signal = service == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mCindSignal : 0;
 
-        Log.d(TAG, "sendDeviceStateChanged. mService=" + mCindService + " mIsSimStateLoaded="
+        Log.d(TAG, "sendDeviceStateChanged. mService=" + service + " mIsSimStateLoaded="
                 + mIsSimStateLoaded + " mSignal=" + signal + " mRoam=" + mCindRoam
                 + " mBatteryCharge=" + mCindBatteryCharge);
         mHeadsetService.onDeviceStateChanged(
@@ -280,7 +399,9 @@
 
         @Override
         public synchronized void onServiceStateChanged(ServiceState serviceState) {
+            Log.d(TAG, "Enter onServiceStateChanged");
             mServiceState = serviceState;
+            int prevService = mCindService;
             int cindService = (serviceState.getState() == ServiceState.STATE_IN_SERVICE)
                     ? HeadsetHalConstants.NETWORK_STATE_AVAILABLE
                     : HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
@@ -297,34 +418,28 @@
             // If this is due to a SIM insertion, we want to defer sending device state changed
             // until all the SIM config is loaded.
             if (cindService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
-                mIsSimStateLoaded = false;
                 sendDeviceStateChanged();
-                return;
             }
-            IntentFilter simStateChangedFilter =
-                    new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
-            mHeadsetService.registerReceiver(new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
-                        // This is a sticky broadcast, so if it's already been loaded,
-                        // this'll execute immediately.
-                        if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(
-                                intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE))) {
-                            mIsSimStateLoaded = true;
-                            sendDeviceStateChanged();
-                            mHeadsetService.unregisterReceiver(this);
-                        }
-                    }
-                }
-            }, simStateChangedFilter);
 
+            //Update the signal strength when the service state changes
+            if (prevService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE &&
+                cindService == HeadsetHalConstants.NETWORK_STATE_AVAILABLE &&
+                mCindSignal == 0) {
+                Log.d(TAG, "Service is available and signal strength was zero, updating the "+
+                           "current signal strength");
+                mCindSignal = mTelephonyManager.getSignalStrength().getLevel() + 1;
+                // +CIND "signal" indicator is always between 0 to 5
+                mCindSignal = Integer.max(Integer.min(mCindSignal, 5), 0);
+                sendDeviceStateChanged();
+            }
+            Log.d(TAG, "Exit onServiceStateChanged");
         }
 
         @Override
         public void onSignalStrengthsChanged(SignalStrength signalStrength) {
             int prevSignal = mCindSignal;
             if (mCindService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
+                Log.d(TAG, "Service is not available, signal strength is set to zero");
                 mCindSignal = 0;
             } else {
                 mCindSignal = signalStrength.getLevel() + 1;
@@ -333,6 +448,7 @@
             mCindSignal = Integer.max(Integer.min(mCindSignal, 5), 0);
             // This results in a lot of duplicate messages, hence this check
             if (prevSignal != mCindSignal) {
+                Log.d(TAG, "Updating the signal strength change to the apps");
                 sendDeviceStateChanged();
             }
         }
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index 2ccc1e4..440e414 100644
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -19,7 +19,10 @@
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
 
 import android.annotation.Nullable;
+import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothDevice;
+
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
@@ -56,6 +59,8 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
+import android.telecom.TelecomManager;
+
 
 /**
  * Provides Bluetooth Headset and Handsfree profile, as a service in the Bluetooth application.
@@ -85,7 +90,7 @@
  */
 public class HeadsetService extends ProfileService {
     private static final String TAG = "HeadsetService";
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
     private static final String DISABLE_INBAND_RINGING_PROPERTY =
             "persist.bluetooth.disableinbandringing";
     private static final ParcelUuid[] HEADSET_UUIDS = {BluetoothUuid.HSP, BluetoothUuid.Handsfree};
@@ -94,6 +99,7 @@
     private static final int DIALING_OUT_TIMEOUT_MS = 10000;
 
     private int mMaxHeadsetConnections = 1;
+    private int mSetMaxConfig;
     private BluetoothDevice mActiveDevice;
     private AdapterService mAdapterService;
     private HandlerThread mStateMachinesThread;
@@ -101,6 +107,7 @@
     private final HashMap<BluetoothDevice, HeadsetStateMachine> mStateMachines = new HashMap<>();
     private HeadsetNativeInterface mNativeInterface;
     private HeadsetSystemInterface mSystemInterface;
+    private HeadsetA2dpSync mHfpA2dpSyncInterface;
     private boolean mAudioRouteAllowed = true;
     // Indicates whether SCO audio needs to be forced to open regardless ANY OTHER restrictions
     private boolean mForceScoAudio;
@@ -116,6 +123,8 @@
     private boolean mStarted;
     private boolean mCreated;
     private static HeadsetService sHeadsetService;
+    private boolean mDisconnectAll;
+    private boolean mIsTwsPlusEnabled = false;
 
     @Override
     public IProfileServiceBinder initBinder() {
@@ -134,8 +143,9 @@
     @Override
     protected boolean start() {
         Log.i(TAG, "start()");
-        if (mStarted) {
-            throw new IllegalStateException("start() called twice");
+        if (sHeadsetService != null) {
+            Log.w(TAG, "HeadsetService is already running");
+            return true;
         }
         // Step 1: Get adapter service, should never be null
         mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
@@ -147,7 +157,31 @@
         mSystemInterface = HeadsetObjectsFactory.getInstance().makeSystemInterface(this);
         mSystemInterface.init();
         // Step 4: Initialize native interface
-        mMaxHeadsetConnections = mAdapterService.getMaxConnectedAudioDevices();
+        mSetMaxConfig = mMaxHeadsetConnections = mAdapterService.getMaxConnectedAudioDevices();
+        if(mAdapterService.isVendorIntfEnabled()) {
+            String twsPlusEnabled = SystemProperties.get("persist.vendor.btstack.enable.twsplus");
+            if (!twsPlusEnabled.isEmpty() && "true".equals(twsPlusEnabled)) {
+                mIsTwsPlusEnabled = true;
+            }
+            Log.i(TAG, "mIsTwsPlusEnabled: " + mIsTwsPlusEnabled);
+            if (mIsTwsPlusEnabled){
+               //set MaxConn to 2 if TWSPLUS enabled
+               mMaxHeadsetConnections = 2;
+            }
+            if (mMaxHeadsetConnections > 2) {
+                //If the set config is more than 2
+                //limit it to 2
+                mMaxHeadsetConnections = 2;
+                mSetMaxConfig = 2;
+            }
+            //Only if the User set config 1 and TWS+ is enabled leaves
+            //these maxConn at 2 and setMaxConfig to 1. this is to avoid
+            //connecting to more than 1 legacy device even though max conns
+            //is set 2 because of TWS+ requirement
+            Log.d(TAG, "Max_HFP_Connections  " + mMaxHeadsetConnections);
+            Log.d(TAG, "mSetMaxConfig  " + mSetMaxConfig);
+        }
+
         mNativeInterface = HeadsetObjectsFactory.getInstance().getNativeInterface();
         // Add 1 to allow a pending device to be connecting or disconnecting
         mNativeInterface.init(mMaxHeadsetConnections + 1, isInbandRingingEnabled());
@@ -163,10 +197,17 @@
         filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
         filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        filter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
+        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(TelecomManager.ACTION_CALL_TYPE);
         registerReceiver(mHeadsetReceiver, filter);
         // Step 7: Mark service as started
+
         setHeadsetService(this);
         mStarted = true;
+
+        mHfpA2dpSyncInterface = new HeadsetA2dpSync(mSystemInterface, this);
+        Log.i(TAG, " HeadsetService Started ");
         return true;
     }
 
@@ -190,16 +231,30 @@
             mInbandRingingRuntimeDisable = false;
             mForceScoAudio = false;
             mAudioRouteAllowed = true;
-            mMaxHeadsetConnections = 1;
+            if(mAdapterService.isVendorIntfEnabled()) {
+                //to enable TWS
+                if (mIsTwsPlusEnabled) {
+                    mMaxHeadsetConnections = 2;
+                } else {
+                   mMaxHeadsetConnections = 1;
+                }
+            } else {
+                mMaxHeadsetConnections = 1;
+            }
             mVoiceRecognitionStarted = false;
             mVirtualCallStarted = false;
             if (mDialingOutTimeoutEvent != null) {
-                mStateMachinesThread.getThreadHandler().removeCallbacks(mDialingOutTimeoutEvent);
+                if (mStateMachinesThread != null) {
+                    mStateMachinesThread.getThreadHandler()
+                      .removeCallbacks(mDialingOutTimeoutEvent);
+                }
                 mDialingOutTimeoutEvent = null;
             }
             if (mVoiceRecognitionTimeoutEvent != null) {
-                mStateMachinesThread.getThreadHandler()
+                if (mStateMachinesThread != null) {
+                    mStateMachinesThread.getThreadHandler()
                         .removeCallbacks(mVoiceRecognitionTimeoutEvent);
+                }
                 mVoiceRecognitionTimeoutEvent = null;
                 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
                     mSystemInterface.getVoiceRecognitionWakeLock().release();
@@ -215,11 +270,15 @@
         mNativeInterface.cleanup();
         // Step 3: Destroy system interface
         mSystemInterface.stop();
-        // Step 2: Stop handler thread
-        mStateMachinesThread.quitSafely();
-        mStateMachinesThread = null;
-        // Step 1: Clear
-        mAdapterService = null;
+        synchronized (mStateMachines) {
+            // Step 2: Stop handler thread
+            if (mStateMachinesThread != null) {
+                mStateMachinesThread.quitSafely();
+            }
+            mStateMachinesThread = null;
+            // Step 1: Clear
+            mAdapterService = null;
+        }
         return true;
     }
 
@@ -249,13 +308,30 @@
      */
     @VisibleForTesting
     public Looper getStateMachinesThreadLooper() {
-        return mStateMachinesThread.getLooper();
+        if (mStateMachinesThread != null) {
+            return mStateMachinesThread.getLooper();
+        }
+        return null;
     }
 
     interface StateMachineTask {
         void execute(HeadsetStateMachine stateMachine);
     }
 
+    // send message to all statemachines in connecting and connected state.
+    private void doForEachConnectedConnectingStateMachine(StateMachineTask task) {
+        synchronized (mStateMachines) {
+            for (BluetoothDevice device :
+                         getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES)) {
+                HeadsetStateMachine stateMachine = mStateMachines.get(device);
+                if (stateMachine == null) {
+                   return;
+                }
+                task.execute(stateMachine);
+            }
+        }
+    }
+
     private boolean doForStateMachine(BluetoothDevice device, StateMachineTask task) {
         synchronized (mStateMachines) {
             HeadsetStateMachine stateMachine = mStateMachines.get(device);
@@ -270,15 +346,21 @@
     private void doForEachConnectedStateMachine(StateMachineTask task) {
         synchronized (mStateMachines) {
             for (BluetoothDevice device : getConnectedDevices()) {
-                task.execute(mStateMachines.get(device));
+                HeadsetStateMachine stateMachine = mStateMachines.get(device);
+                if (stateMachine == null) {
+                    continue;
+                }
+                task.execute(stateMachine);
             }
         }
     }
 
     void onDeviceStateChanged(HeadsetDeviceState deviceState) {
-        doForEachConnectedStateMachine(
+        synchronized (mStateMachines) {
+            doForEachConnectedStateMachine(
                 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.DEVICE_STATE_CHANGED,
                         deviceState));
+        }
     }
 
     /**
@@ -298,11 +380,15 @@
                     case HeadsetHalConstants.CONNECTION_STATE_CONNECTING: {
                         // Create new state machine if none is found
                         if (stateMachine == null) {
-                            stateMachine = HeadsetObjectsFactory.getInstance()
+                            if (mStateMachinesThread != null) {
+                                stateMachine = HeadsetObjectsFactory.getInstance()
                                     .makeStateMachine(stackEvent.device,
                                             mStateMachinesThread.getLooper(), this, mAdapterService,
                                             mNativeInterface, mSystemInterface);
-                            mStateMachines.put(stackEvent.device, stateMachine);
+                                mStateMachines.put(stackEvent.device, stateMachine);
+                            } else {
+                                Log.w(TAG, "messageFromNative: mStateMachinesThread is null");
+                            }
                         }
                         break;
                     }
@@ -340,8 +426,10 @@
                 case AudioManager.VOLUME_CHANGED_ACTION: {
                     int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                     if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
-                        doForEachConnectedStateMachine(stateMachine -> stateMachine.sendMessage(
+                       synchronized (mStateMachines) {
+                          doForEachConnectedStateMachine(stateMachine -> stateMachine.sendMessage(
                                 HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED, intent));
+                       }
                     }
                     break;
                 }
@@ -388,6 +476,23 @@
                     }
                     break;
                 }
+                case BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED: {
+                    logD("Received BluetoothA2dp Play State changed");
+                    mHfpA2dpSyncInterface.updateA2DPPlayingState(intent);
+                    break;
+                }
+                case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: {
+                    logD("Received BluetoothA2dp Connection State changed");
+                    mHfpA2dpSyncInterface.updateA2DPConnectionState(intent);
+                    break;
+                }
+                case TelecomManager.ACTION_CALL_TYPE: {
+                    logD("Received BluetoothHeadset action call type");
+                    synchronized (mStateMachines) {
+                        doForEachConnectedStateMachine(stateMachine -> stateMachine.sendMessage(
+                            HeadsetStateMachine.UPDATE_CALL_TYPE, intent));
+                    }
+                }
                 default:
                     Log.w(TAG, "Unknown action " + action);
             }
@@ -463,6 +568,15 @@
             return service.getDevicesMatchingConnectionStates(states);
         }
 
+        public List<BluetoothDevice> getAllDevicesMatchingConnectionStates(int[] states) {
+            HeadsetService service = getService();
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
+            return service.getAllDevicesMatchingConnectionStates(states);
+        }
+
+
         @Override
         public int getConnectionState(BluetoothDevice device) {
             HeadsetService service = getService();
@@ -675,6 +789,83 @@
         sHeadsetService = instance;
     }
 
+    public BluetoothDevice getTwsPlusConnectedPeer(BluetoothDevice device) {
+        AdapterService adapterService = AdapterService.getAdapterService();
+        if (!adapterService.isTwsPlusDevice(device)) {
+            logD("getTwsPlusConnectedPeer: Not a TWS+ device");
+            return null;
+        }
+        List<BluetoothDevice> connDevices = getConnectedDevices();
+
+        int size = connDevices.size();
+        for(int i = 0; i < size; i++) {
+            BluetoothDevice ConnectedDevice = connDevices.get(i);
+            if (adapterService.getTwsPlusPeerAddress(device).equals(ConnectedDevice.getAddress())) {
+                return ConnectedDevice;
+            }
+        }
+        return null;
+    }
+
+    private boolean isConnectionAllowed(BluetoothDevice device,
+                                           List<BluetoothDevice> connDevices
+                                           ) {
+        AdapterService adapterService = AdapterService.getAdapterService();
+        boolean allowSecondHfConnection = false;
+
+        if (!mIsTwsPlusEnabled && adapterService.isTwsPlusDevice(device)) {
+           logD("No TWSPLUS connections as It is not Enabled");
+           return false;
+        }
+
+        if (connDevices.size() == 0) {
+            allowSecondHfConnection = true;
+        } else {
+            BluetoothDevice connectedDev = connDevices.get(0);
+            if (adapterService.isTwsPlusDevice(connectedDev)) {
+                //If connected device is TWSPlus device
+                //Allow connection only if the outgoing is peer of TWS connected earbud
+                if (adapterService.isTwsPlusDevice(device)&&
+                   adapterService.getTwsPlusPeerAddress(device).equals(connectedDev.getAddress())) {
+                   allowSecondHfConnection = true;
+                } else {
+                   allowSecondHfConnection = false;
+                   if (connDevices.size() == 1) {
+                       mDisconnectAll = true;
+                   }
+                }
+            } else {
+                //if Connected device is not TWS
+                if (adapterService.isTwsPlusDevice(device)) {
+                    //outgoing connection is TWSP
+                    allowSecondHfConnection = false;
+                    if (connDevices.size() == 1) {
+                        //only if connected devices is 1
+                        //disconnect it and override it with tws+
+                        mDisconnectAll = true;
+                    }
+                } else {
+                    //Outgoing connection is legacy device
+                    //For legacy case use the config set by User
+                   if (connDevices.size() < mSetMaxConfig) {
+                      allowSecondHfConnection = true;
+                   } else {
+                      allowSecondHfConnection = false;
+                   }
+               }
+            }
+            Log.v(TAG, "isTwsPlusDevice for " + device +
+                     "is"+ adapterService.isTwsPlusDevice(device));
+            Log.v(TAG, "TWS Peer Addr: " +
+                      adapterService.getTwsPlusPeerAddress(device));
+            Log.v(TAG, "Connected device" + connectedDev.getAddress());
+        }
+
+        Log.v(TAG, "allowSecondHfConnection: " + allowSecondHfConnection);
+        Log.v(TAG, "DisconnectAll: " + mDisconnectAll);
+        return allowSecondHfConnection;
+    }
+
     public boolean connect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
@@ -691,10 +882,14 @@
             Log.i(TAG, "connect: device=" + device + ", " + Utils.getUidPidString());
             HeadsetStateMachine stateMachine = mStateMachines.get(device);
             if (stateMachine == null) {
-                stateMachine = HeadsetObjectsFactory.getInstance()
+                if (mStateMachinesThread != null) {
+                    stateMachine = HeadsetObjectsFactory.getInstance()
                         .makeStateMachine(device, mStateMachinesThread.getLooper(), this,
                                 mAdapterService, mNativeInterface, mSystemInterface);
-                mStateMachines.put(device, stateMachine);
+                    mStateMachines.put(device, stateMachine);
+                } else {
+                    Log.w(TAG, "connect: mStateMachinesThread is null");
+                }
             }
             int connectionState = stateMachine.getConnectionState();
             if (connectionState == BluetoothProfile.STATE_CONNECTED
@@ -704,11 +899,24 @@
                 return false;
             }
             List<BluetoothDevice> connectingConnectedDevices =
-                    getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+                    getAllDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+            addAllDevicesPendingRetryConnect(connectingConnectedDevices);
             boolean disconnectExisting = false;
-            if (connectingConnectedDevices.size() >= mMaxHeadsetConnections) {
+            mDisconnectAll = false;
+            if (connectingConnectedDevices.size() == 0) {
+                 Log.e(TAG, "No Connected devices!");
+            }
+            if (!isConnectionAllowed(device, connectingConnectedDevices)) {
                 // When there is maximum one device, we automatically disconnect the current one
                 if (mMaxHeadsetConnections == 1) {
+                    if (!mIsTwsPlusEnabled && mAdapterService.isTwsPlusDevice(device)) {
+                        Log.w(TAG, "Connection attemp to TWS+ when not enabled, Rejecting it");
+                        return false;
+                    } else {
+                        disconnectExisting = true;
+                    }
+                } else if (mDisconnectAll) {
+                    //In Dual HF case
                     disconnectExisting = true;
                 } else {
                     Log.w(TAG, "Max connection has reached, rejecting connection to " + device);
@@ -726,7 +934,7 @@
         return true;
     }
 
-    boolean disconnect(BluetoothDevice device) {
+    public boolean disconnect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Log.i(TAG, "disconnect: device=" + device + ", " + Utils.getUidPidString());
         synchronized (mStateMachines) {
@@ -747,6 +955,18 @@
         return true;
     }
 
+    public boolean isInCall() {
+        boolean isCallOngoing = mSystemInterface.isInCall();
+        Log.d(TAG," isInCall " + isCallOngoing);
+        return isCallOngoing;
+    }
+
+    public boolean isRinging() {
+        boolean isRingOngoing = mSystemInterface.isRinging();
+        Log.d(TAG," isRinging " + isRingOngoing);
+        return isRingOngoing;
+    }
+
     public List<BluetoothDevice> getConnectedDevices() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         ArrayList<BluetoothDevice> devices = new ArrayList<>();
@@ -760,6 +980,54 @@
         return devices;
     }
 
+    private void addAllDevicesPendingRetryConnect(List<BluetoothDevice> devices) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        Log.d(TAG, " add all devices pending retry connect");
+        synchronized (mStateMachines) {
+            for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
+                BluetoothDevice device = stateMachine.getDevice();
+                if ((stateMachine.isPendingRetryConnect() == true) &&
+                    (!devices.contains(device))) {
+                    devices.add(device);
+                    Log.d(TAG, " add pending retry connect device: " + device);
+                }
+            }
+        }
+    }
+
+    /**
+     * Helper method to get all devices with matching connection state
+     *
+     */
+    private List<BluetoothDevice> getAllDevicesMatchingConnectionStates(int[] states) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        ArrayList<BluetoothDevice> devices = new ArrayList<>();
+        if (states == null) {
+            Log.e(TAG, "->States is null");
+            return devices;
+        }
+        final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+        if (bondedDevices == null) {
+            Log.e(TAG, "->Bonded device is null");
+            return devices;
+        }
+        synchronized (mStateMachines) {
+            for (BluetoothDevice device : bondedDevices) {
+
+                int connectionState = getConnectionState(device);
+                Log.e(TAG, "Connec state for: " + device + "is" + connectionState);
+                for (int state : states) {
+                    if (connectionState == state) {
+                        devices.add(device);
+                        Log.e(TAG, "Adding device: " + device);
+                        break;
+                    }
+                }
+            }
+        }
+        return devices;
+    }
+
     /**
      * Same as the API method {@link BluetoothHeadset#getDevicesMatchingConnectionStates(int[])}
      *
@@ -779,10 +1047,7 @@
         }
         synchronized (mStateMachines) {
             for (BluetoothDevice device : bondedDevices) {
-                final ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
-                if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) {
-                    continue;
-                }
+
                 int connectionState = getConnectionState(device);
                 for (int state : states) {
                     if (connectionState == state) {
@@ -863,15 +1128,20 @@
                             + ", fall back to requesting device");
                     device = mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice;
                 }
-                mStateMachinesThread.getThreadHandler()
-                        .removeCallbacks(mVoiceRecognitionTimeoutEvent);
+                if (mStateMachinesThread != null) {
+                    mStateMachinesThread.getThreadHandler()
+                           .removeCallbacks(mVoiceRecognitionTimeoutEvent);
+                } else {
+                    Log.w(TAG, "startVoiceRecognition: mStateMachinesThread is null");
+                }
                 mVoiceRecognitionTimeoutEvent = null;
                 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
                     mSystemInterface.getVoiceRecognitionWakeLock().release();
                 }
                 pendingRequestByHeadset = true;
             }
-            if (!Objects.equals(device, mActiveDevice) && !setActiveDevice(device)) {
+            if (!Objects.equals(device, mActiveDevice) &&
+                  !mAdapterService.isTwsPlusDevice(device) && !setActiveDevice(device)) {
                 Log.w(TAG, "startVoiceRecognition: failed to set " + device + " as active");
                 return false;
             }
@@ -893,7 +1163,6 @@
             } else {
                 stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START, device);
             }
-            stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device);
         }
         return true;
     }
@@ -929,11 +1198,24 @@
         return true;
     }
 
-    boolean isAudioOn() {
+    public boolean isAudioOn() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return getNonIdleAudioDevices().size() > 0;
+        int numConnectedAudioDevices = getNonIdleAudioDevices().size();
+        Log.d(TAG," isAudioOn: The number of audio connected devices "
+                 + numConnectedAudioDevices);
+        return numConnectedAudioDevices > 0;
     }
 
+    public boolean isScoOrCallActive() {
+      Log.d(TAG, "isScoOrCallActive(): Call Active:" + mSystemInterface.isInCall() +
+                                       "Call is Ringing:" + mSystemInterface.isInCall() +
+                                       "SCO is Active:" + isAudioOn());
+      if (mSystemInterface.isInCall() || (mSystemInterface.isRinging()) || isAudioOn()) {
+          return true;
+      } else {
+          return false;
+      }
+    }
     boolean isAudioConnected(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         synchronized (mStateMachines) {
@@ -1035,6 +1317,9 @@
                         Log.w(TAG, "setActiveDevice: disconnectAudio failed on " + mActiveDevice);
                     }
                 }
+                if (!mNativeInterface.setActiveDevice(null)) {
+                    Log.w(TAG, "setActiveDevice: Cannot set active device as null in native layer");
+                }
                 mActiveDevice = null;
                 broadcastActiveDevice(null);
                 return true;
@@ -1048,6 +1333,14 @@
                         + " as active, device is not connected");
                 return false;
             }
+            if (mActiveDevice != null && mAdapterService.isTwsPlusDevice(device) &&
+                mAdapterService.isTwsPlusDevice(mActiveDevice) &&
+                !Objects.equals(device, mActiveDevice) &&
+                getConnectionState(mActiveDevice) == BluetoothProfile.STATE_CONNECTED) {
+                Log.d(TAG,"Ignore setActiveDevice request");
+                return false;
+            }
+
             if (!mNativeInterface.setActiveDevice(device)) {
                 Log.e(TAG, "setActiveDevice: Cannot set " + device + " as active in native layer");
                 return false;
@@ -1064,13 +1357,16 @@
                 }
                 broadcastActiveDevice(mActiveDevice);
             } else if (shouldPersistAudio()) {
-                broadcastActiveDevice(mActiveDevice);
-                if (!connectAudio(mActiveDevice)) {
-                    Log.e(TAG, "setActiveDevice: fail to connectAudio to " + mActiveDevice);
-                    mActiveDevice = previousActiveDevice;
-                    mNativeInterface.setActiveDevice(previousActiveDevice);
-                    return false;
+                boolean isPts = SystemProperties.getBoolean("vendor.bt.pts.certification", false);
+                if (!isPts) {
+                    if (!connectAudio(mActiveDevice)) {
+                        Log.e(TAG, "setActiveDevice: fail to connectAudio to " + mActiveDevice);
+                        mActiveDevice = previousActiveDevice;
+                        mNativeInterface.setActiveDevice(previousActiveDevice);
+                        return false;
+                    }
                 }
+                broadcastActiveDevice(mActiveDevice);
             } else {
                 broadcastActiveDevice(mActiveDevice);
             }
@@ -1124,9 +1420,12 @@
                 return true;
             }
             if (isAudioOn()) {
-                Log.w(TAG, "connectAudio: audio is not idle, current audio devices are "
-                        + Arrays.toString(getNonIdleAudioDevices().toArray()));
-                return false;
+                //SCO is connecting or connected.
+                //Return true to telephony
+                Log.w(TAG, "connectAudio: audio is not idle, current audio devices are: "
+                        + Arrays.toString(getNonIdleAudioDevices().toArray()) + 
+                        " ,returning true");
+                return true;
             }
             stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device);
         }
@@ -1188,6 +1487,13 @@
         }
     }
 
+    boolean isVRStarted() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        synchronized (mStateMachines) {
+            return mVoiceRecognitionStarted;
+        }
+    }
+
     private boolean startScoUsingVirtualVoiceCall() {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Log.i(TAG, "startScoUsingVirtualVoiceCall: " + Utils.getUidPidString());
@@ -1220,6 +1526,7 @@
                 return false;
             }
             mVirtualCallStarted = true;
+            mSystemInterface.getHeadsetPhoneState().setIsCsCall(false);
             // Send virtual phone state changed to initialize SCO
             phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0, true);
             phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0, true);
@@ -1292,7 +1599,8 @@
                     return false;
                 }
             }
-            if (!setActiveDevice(fromDevice)) {
+            if (!mAdapterService.isTwsPlusDevice(fromDevice) &&
+                !setActiveDevice(fromDevice)) {
                 Log.e(TAG, "dialOutgoingCall failed to set active device to " + fromDevice);
                 return false;
             }
@@ -1301,8 +1609,12 @@
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startActivity(intent);
             mDialingOutTimeoutEvent = new DialingOutTimeoutEvent(fromDevice);
-            mStateMachinesThread.getThreadHandler()
+            if (mStateMachinesThread != null) {
+                mStateMachinesThread.getThreadHandler()
                     .postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS);
+            } else {
+                Log.w(TAG, "dialOutgoingCall: mStateMachinesThread is null");
+            }
             return true;
         }
     }
@@ -1381,7 +1693,7 @@
                         + ", already pending by " + mVoiceRecognitionTimeoutEvent);
                 return false;
             }
-            if (!setActiveDevice(fromDevice)) {
+            if (!mAdapterService.isTwsPlusDevice(fromDevice) && !setActiveDevice(fromDevice)) {
                 Log.w(TAG, "startVoiceRecognitionByHeadset: failed to set " + fromDevice
                         + " as active");
                 return false;
@@ -1406,8 +1718,12 @@
                 return false;
             }
             mVoiceRecognitionTimeoutEvent = new VoiceRecognitionTimeoutEvent(fromDevice);
-            mStateMachinesThread.getThreadHandler()
+            if (mStateMachinesThread != null) {
+                mStateMachinesThread.getThreadHandler()
                     .postDelayed(mVoiceRecognitionTimeoutEvent, sStartVrTimeoutMs);
+            } else {
+                Log.w(TAG, "startVoiceRecognitionByHeadset: mStateMachinesThread is null");
+            }
             if (!mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
                 mSystemInterface.getVoiceRecognitionWakeLock().acquire(sStartVrTimeoutMs);
             }
@@ -1432,8 +1748,12 @@
                 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
                     mSystemInterface.getVoiceRecognitionWakeLock().release();
                 }
-                mStateMachinesThread.getThreadHandler()
+                if (mStateMachinesThread != null) {
+                    mStateMachinesThread.getThreadHandler()
                         .removeCallbacks(mVoiceRecognitionTimeoutEvent);
+                } else {
+                    Log.w(TAG, "stopVoiceRecognitionByHeadset: mStateMachinesThread is null");
+                }
                 mVoiceRecognitionTimeoutEvent = null;
             }
             if (mVoiceRecognitionStarted) {
@@ -1455,6 +1775,10 @@
             int type, boolean isVirtualCall) {
         enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
         synchronized (mStateMachines) {
+            if (mStateMachinesThread == null) {
+                Log.w(TAG, "mStateMachinesThread is null, returning");
+                return;
+            }
             // Should stop all other audio mode in this case
             if ((numActive + numHeld) > 0 || callState != HeadsetHalConstants.CALL_STATE_IDLE) {
                 if (!isVirtualCall && mVirtualCallStarted) {
@@ -1465,6 +1789,12 @@
                     // stop voice recognition if there is any incoming call
                     stopVoiceRecognition(mActiveDevice);
                 }
+            } else {
+                // ignore CS non-call state update when virtual call started
+                if (!isVirtualCall && mVirtualCallStarted) {
+                    Log.i(TAG, "Ignore CS non-call state update");
+                    return;
+                }
             }
             if (mDialingOutTimeoutEvent != null) {
                 // Send result to state machine when dialing starts
@@ -1484,38 +1814,55 @@
                     }
                 }
             }
-        }
-        mStateMachinesThread.getThreadHandler().post(() -> {
-            boolean isCallIdleBefore = mSystemInterface.isCallIdle();
-            mSystemInterface.getHeadsetPhoneState().setNumActiveCall(numActive);
-            mSystemInterface.getHeadsetPhoneState().setNumHeldCall(numHeld);
-            mSystemInterface.getHeadsetPhoneState().setCallState(callState);
-            // Suspend A2DP when call about is about to become active
-            if (callState != HeadsetHalConstants.CALL_STATE_DISCONNECTED
-                    && !mSystemInterface.isCallIdle() && isCallIdleBefore) {
-                mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
-            }
-        });
-        doForEachConnectedStateMachine(
-                stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
+            mStateMachinesThread.getThreadHandler().post(() -> {
+                mSystemInterface.getHeadsetPhoneState().setNumActiveCall(numActive);
+                mSystemInterface.getHeadsetPhoneState().setNumHeldCall(numHeld);
+                mSystemInterface.getHeadsetPhoneState().setCallState(callState);
+            });
+            List<BluetoothDevice> availableDevices =
+                        getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+            if(availableDevices.size() > 0) {
+                Log.i(TAG, "Update the phoneStateChanged status to connecting and " +
+                           "connected devices");
+                doForEachConnectedConnectingStateMachine(
+                   stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
                         new HeadsetCallState(numActive, numHeld, callState, number, type)));
-        mStateMachinesThread.getThreadHandler().post(() -> {
-            if (callState == HeadsetHalConstants.CALL_STATE_IDLE
-                    && mSystemInterface.isCallIdle() && !isAudioOn()) {
-                // Resume A2DP when call ended and SCO is not connected
-                mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
+                mStateMachinesThread.getThreadHandler().post(() -> {
+                    if (!(mSystemInterface.isInCall() || mSystemInterface.isRinging())) {
+                        Log.i(TAG, "no call, sending resume A2DP message to state machines");
+                        for (BluetoothDevice device : availableDevices) {
+                            HeadsetStateMachine stateMachine = mStateMachines.get(device);
+                            if (stateMachine == null) {
+                                Log.w(TAG, "phoneStateChanged: device " + device +
+                                       " was never connected/connecting");
+                                continue;
+                            }
+                            stateMachine.sendMessage(HeadsetStateMachine.RESUME_A2DP);
+                        }
+                    }
+                });
+            } else {
+                mStateMachinesThread.getThreadHandler().post(() -> {
+                    if (!(mSystemInterface.isInCall() || mSystemInterface.isRinging())) {
+                        //If no device is connected, resume A2DP if there is no call
+                        Log.i(TAG, "No device is connected and no call, " +
+                                   "set A2DPsuspended to false");
+                        mHfpA2dpSyncInterface.releaseA2DP(null);
+                    }
+                });
             }
-        });
-
+        }
     }
 
     private void clccResponse(int index, int direction, int status, int mode, boolean mpty,
             String number, int type) {
         enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
-        doForEachConnectedStateMachine(
+        synchronized (mStateMachines) {
+           doForEachConnectedStateMachine(
                 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE,
                         new HeadsetClccResponse(index, direction, status, mode, mpty, number,
                                 type)));
+        }
     }
 
     private boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
@@ -1544,9 +1891,12 @@
     }
 
     boolean isInbandRingingEnabled() {
+        boolean returnVal;
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return BluetoothHeadset.isInbandRingingSupported(this) && !SystemProperties.getBoolean(
-                DISABLE_INBAND_RINGING_PROPERTY, false) && !mInbandRingingRuntimeDisable;
+        returnVal = BluetoothHeadset.isInbandRingingSupported(this) && !SystemProperties.getBoolean(
+                DISABLE_INBAND_RINGING_PROPERTY, true) && !mInbandRingingRuntimeDisable;
+        Log.d(TAG, "isInbandRingingEnabled returning: " + returnVal);
+        return returnVal;
     }
 
     /**
@@ -1565,7 +1915,7 @@
                     getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
             if (fromState != BluetoothProfile.STATE_CONNECTED
                     && toState == BluetoothProfile.STATE_CONNECTED) {
-                if (audioConnectableDevices.size() > 1) {
+                if (audioConnectableDevices.size() > 1 && isInbandRingingEnabled()) {
                     mInbandRingingRuntimeDisable = true;
                     doForEachConnectedStateMachine(
                             stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR,
@@ -1575,17 +1925,50 @@
             }
             if (fromState != BluetoothProfile.STATE_DISCONNECTED
                     && toState == BluetoothProfile.STATE_DISCONNECTED) {
-                if (audioConnectableDevices.size() <= 1) {
+                if (audioConnectableDevices.size() <= 1 ) {
                     mInbandRingingRuntimeDisable = false;
-                    doForEachConnectedStateMachine(
+                    if(isInbandRingingEnabled()) {
+                        doForEachConnectedStateMachine(
                             stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR,
-                                    1));
+                                   1));
+                    }
                 }
                 if (device.equals(mActiveDevice)) {
-                    setActiveDevice(null);
+                    AdapterService adapterService = AdapterService.getAdapterService();
+                    if (adapterService.isTwsPlusDevice(device)) {
+                        //if the disconnected device is a tws+ device
+                        // and if peer device is connected, set the peer
+                        // as an active device
+                        BluetoothDevice peerDevice = getTwsPlusConnectedPeer(device);
+                        if (peerDevice != null) {
+                            setActiveDevice(peerDevice);
+                        }
+                    } else {
+                        setActiveDevice(null);
+                    }
                 }
             }
         }
+
+        // if active device is null, SLC connected, make this device as active.
+        if (fromState == BluetoothProfile.STATE_CONNECTING &&
+             toState == BluetoothProfile.STATE_CONNECTED &&
+             mActiveDevice == null) {
+             Log.i(TAG, "onConnectionStateChangedFromStateMachine: SLC connected, no active"
+                              + " is present. Setting active device to " + device);
+             setActiveDevice(device);
+        }
+    }
+
+    public HeadsetA2dpSync getHfpA2DPSyncInterface(){
+        return mHfpA2dpSyncInterface;
+    }
+
+    public void sendA2dpStateChangeUpdate(int state) {
+        Log.d(TAG," sendA2dpStateChange newState = " + state);
+        doForEachConnectedConnectingStateMachine(
+              stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.A2DP_STATE_CHANGED,
+                                    state));
     }
 
     /**
@@ -1606,8 +1989,19 @@
     }
 
     private boolean shouldCallAudioBeActive() {
-        return mSystemInterface.isInCall() || (mSystemInterface.isRinging()
-                && isInbandRingingEnabled());
+        boolean retVal = false;
+        // When the call is active/held, the call audio must be active
+        if (mSystemInterface.getHeadsetPhoneState().getNumActiveCall() > 0 ||
+            mSystemInterface.getHeadsetPhoneState().getNumHeldCall() > 0 ) {
+            Log.d(TAG, "shouldCallAudioBeActive(): returning true, since call is active/held");
+            return true;
+        }
+        // When call is in  ringing state, SCO should not be accepted if
+        // in-band ringtone is not enabled
+        retVal = (mSystemInterface.isInCall() && !mSystemInterface.isRinging() )||
+                 (mSystemInterface.isRinging() && isInbandRingingEnabled());
+        Log.d(TAG, "shouldCallAudioBeActive() returning " + retVal);
+        return retVal;
     }
 
     /**
@@ -1636,31 +2030,47 @@
             int toState) {
         synchronized (mStateMachines) {
             if (toState == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-                if (fromState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-                    if (mActiveDevice != null && !mActiveDevice.equals(device)
-                            && shouldPersistAudio()) {
-                        if (!connectAudio(mActiveDevice)) {
-                            Log.w(TAG, "onAudioStateChangedFromStateMachine, failed to connect"
-                                    + " audio to new " + "active device " + mActiveDevice
-                                    + ", after " + device + " is disconnected from SCO");
-                        }
-                    }
-                }
                 if (mVoiceRecognitionStarted) {
                     if (!stopVoiceRecognitionByHeadset(device)) {
                         Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop voice "
                                 + "recognition");
+                    } else {
+                        final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+                        if (stateMachine != null) {
+                            Log.d(TAG, "onAudioStateChangedFromStateMachine: send +bvra:0");
+                            stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP,
+                                    device);
+                        }
                     }
                 }
-                if (mVirtualCallStarted) {
-                    if (!stopScoUsingVirtualVoiceCall()) {
-                        Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop virtual "
-                                + "voice call");
+
+                if (mAdapterService.isTwsPlusDevice(device) &&
+                    isAudioConnected(getTwsPlusConnectedPeer(device))) {
+                    Log.w(TAG, "Ignore stop virtuall voice call if the other TWS+ device is "
+                            + "audio connected");
+                } else {
+                    if (mVirtualCallStarted) {
+                        if (!stopScoUsingVirtualVoiceCall()) {
+                            Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop "
+                                    + "virtual voice call");
+                        }
                     }
                 }
-                // Unsuspend A2DP when SCO connection is gone and call state is idle
-                if (mSystemInterface.isCallIdle()) {
-                    mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
+
+                //Transfer SCO is not needed for TWS+ devices
+                if (!mAdapterService.isTwsPlusDevice(device)) {
+                    // trigger SCO after SCO disconnected with previous active
+                    // device
+                    if (mActiveDevice != null && !mActiveDevice.equals(device) &&
+                                 shouldPersistAudio()) {
+                        Log.d(TAG, "onAudioStateChangedFromStateMachine: triggering SCO with device "
+                              + mActiveDevice);
+                       if (!connectAudio(mActiveDevice)) {
+                           Log.w(TAG, "onAudioStateChangedFromStateMachine, failed to connect"
+                              + " audio to new " + "active device " + mActiveDevice
+                              + ", after " + device + " is disconnected from SCO");
+                       }
+                    }
                 }
             }
         }
@@ -1683,41 +2093,44 @@
      */
     public boolean okToAcceptConnection(BluetoothDevice device) {
         // Check if this is an incoming connection in Quiet mode.
+        boolean isPts = SystemProperties.getBoolean("vendor.bt.pts.certification", false);
         if (mAdapterService.isQuietModeEnabled()) {
             Log.w(TAG, "okToAcceptConnection: return false as quiet mode enabled");
             return false;
         }
-        // Check priority and accept or reject the connection.
-        // Note: Logic can be simplified, but keeping it this way for readability
-        int priority = getPriority(device);
-        int bondState = mAdapterService.getBondState(device);
-        // If priority is undefined, it is likely that service discovery has not completed and peer
-        // initiated the connection. Allow this connection only if the device is bonded or bonding
-        boolean serviceDiscoveryPending = (priority == BluetoothProfile.PRIORITY_UNDEFINED) && (
-                bondState == BluetoothDevice.BOND_BONDING
-                        || bondState == BluetoothDevice.BOND_BONDED);
-        // Also allow connection when device is bonded/bonding and priority is ON/AUTO_CONNECT.
-        boolean isEnabled = (priority == BluetoothProfile.PRIORITY_ON
-                || priority == BluetoothProfile.PRIORITY_AUTO_CONNECT) && (
-                bondState == BluetoothDevice.BOND_BONDED
-                        || bondState == BluetoothDevice.BOND_BONDING);
-        if (!serviceDiscoveryPending && !isEnabled) {
-            // Otherwise, reject the connection if no service discovery is pending and priority is
-            // neither PRIORITY_ON nor PRIORITY_AUTO_CONNECT
-            Log.w(TAG,
-                    "okToConnect: return false, priority=" + priority + ", bondState=" + bondState);
-            return false;
+        if(!isPts) {
+            // Check priority and accept or reject the connection.
+            // Note: Logic can be simplified, but keeping it this way for readability
+            int priority = getPriority(device);
+            int bondState = mAdapterService.getBondState(device);
+            // If priority is undefined, it is likely that service discovery has not completed and peer
+            // initiated the connection. Allow this connection only if the device is bonded or bonding
+            boolean serviceDiscoveryPending = (priority == BluetoothProfile.PRIORITY_UNDEFINED) && (
+                    bondState == BluetoothDevice.BOND_BONDING
+                            || bondState == BluetoothDevice.BOND_BONDED);
+            // Also allow connection when device is bonded/bonding and priority is ON/AUTO_CONNECT.
+            boolean isEnabled = (priority == BluetoothProfile.PRIORITY_ON
+                    || priority == BluetoothProfile.PRIORITY_AUTO_CONNECT) && (
+                    bondState == BluetoothDevice.BOND_BONDED
+                            || bondState == BluetoothDevice.BOND_BONDING);
+            if (!serviceDiscoveryPending && !isEnabled) {
+                // Otherwise, reject the connection if no service discovery is pending and priority is
+                // neither PRIORITY_ON nor PRIORITY_AUTO_CONNECT
+                Log.w(TAG,
+                        "okToConnect: return false, priority=" + priority + ", bondState=" + bondState);
+                return false;
+            }
         }
         List<BluetoothDevice> connectingConnectedDevices =
-                getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
-        if (connectingConnectedDevices.size() >= mMaxHeadsetConnections) {
+                getAllDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+        addAllDevicesPendingRetryConnect(connectingConnectedDevices);
+        if (!isConnectionAllowed(device, connectingConnectedDevices)) {
             Log.w(TAG, "Maximum number of connections " + mMaxHeadsetConnections
                     + " was reached, rejecting connection from " + device);
             return false;
         }
         return true;
     }
-
     /**
      * Checks if SCO should be connected at current system state
      *
@@ -1726,10 +2139,13 @@
      */
     public boolean isScoAcceptable(BluetoothDevice device) {
         synchronized (mStateMachines) {
-            if (device == null || !device.equals(mActiveDevice)) {
-                Log.w(TAG, "isScoAcceptable: rejected SCO since " + device
+            //allow 2nd eSCO from non-active tws+ earbud as well
+            if (!mAdapterService.isTwsPlusDevice(device)) {
+                if (device == null || !device.equals(mActiveDevice)) {
+                    Log.w(TAG, "isScoAcceptable: rejected SCO since " + device
                         + " is not the current active device " + mActiveDevice);
-                return false;
+                    return false;
+                }
             }
             if (mForceScoAudio) {
                 return true;
@@ -1738,6 +2154,15 @@
                 Log.w(TAG, "isScoAcceptable: rejected SCO since audio route is not allowed");
                 return false;
             }
+            /* if in-band ringtone is not enabled and if there is
+                no active/held/dialling/alerting call, return false */
+            if (isRinging() && !isInbandRingingEnabled() &&
+                !(mSystemInterface.getHeadsetPhoneState().getNumActiveCall() > 0 ||
+                  mSystemInterface.getHeadsetPhoneState().getNumHeldCall() > 0 )) {
+                Log.w(TAG, "isScoAcceptable: rejected SCO since MT call in ringing," +
+                            "in-band ringing not enabled");
+                return false;
+            }
             if (mVoiceRecognitionStarted || mVirtualCallStarted) {
                 return true;
             }
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index d25d9ed..8a2eab3 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -25,11 +25,15 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.support.annotation.VisibleForTesting;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.PhoneStateListener;
 import android.util.Log;
+import android.os.SystemProperties;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
 
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
@@ -41,9 +45,13 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Scanner;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.Iterator;
+import android.telecom.TelecomManager;
 
 /**
  * A Bluetooth Handset StateMachine
@@ -68,7 +76,7 @@
 @VisibleForTesting
 public class HeadsetStateMachine extends StateMachine {
     private static final String TAG = "HeadsetStateMachine";
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
 
     private static final String HEADSET_NAME = "bt_headset_name";
     private static final String HEADSET_NREC = "bt_headset_nrec";
@@ -95,20 +103,66 @@
     static final int DIALING_OUT_RESULT = 14;
     static final int VOICE_RECOGNITION_RESULT = 15;
 
+    static final int QUERY_PHONE_STATE_AT_SLC = 18;
+    static final int SEND_INCOMING_CALL_IND = 19;
+    static final int VOIP_CALL_STATE_CHANGED_ALERTING = 20;
+    static final int VOIP_CALL_STATE_CHANGED_ACTIVE = 21;
+    static final int CS_CALL_STATE_CHANGED_ALERTING = 22;
+    static final int CS_CALL_STATE_CHANGED_ACTIVE = 23;
+    static final int A2DP_STATE_CHANGED = 24;
+    static final int UPDATE_CALL_TYPE = 25;
+    static final int RESUME_A2DP = 26;
+
     static final int STACK_EVENT = 101;
     private static final int CLCC_RSP_TIMEOUT = 104;
+    private static final int PROCESS_CPBR = 105;
 
     private static final int CONNECT_TIMEOUT = 201;
 
     private static final int CLCC_RSP_TIMEOUT_MS = 5000;
+    private static final int QUERY_PHONE_STATE_CHANGED_DELAYED = 100;
     // NOTE: the value is not "final" - it is modified in the unit tests
     @VisibleForTesting static int sConnectTimeoutMs = 30000;
 
     private static final HeadsetAgIndicatorEnableState DEFAULT_AG_INDICATOR_ENABLE_STATE =
             new HeadsetAgIndicatorEnableState(true, true, true, true);
 
+    // delay call indicators and some remote devices are not able to handle
+    // indicators back to back, especially in VOIP scenarios.
+    /* Delay between call dialling, alerting updates for VOIP call */
+    private static final int VOIP_CALL_ALERTING_DELAY_TIME_MSEC = 800;
+    /* Delay between call alerting, active updates for VOIP call */
+    private static final int VOIP_CALL_ACTIVE_DELAY_TIME_MSEC =
+                               VOIP_CALL_ALERTING_DELAY_TIME_MSEC + 50;
+    private int CS_CALL_ALERTING_DELAY_TIME_MSEC = 800;
+    private int CS_CALL_ACTIVE_DELAY_TIME_MSEC = 10;
+    private static final int INCOMING_CALL_IND_DELAY = 200;
+    private static final int MAX_RETRY_CONNECT_COUNT = 2;
+    // Blacklist remote device addresses to send incoimg call indicators with delay of 200ms
+    private static final String [] BlacklistDeviceAddrToDelayCallInd =
+                                                               {"00:15:83", /* Beiqi Carkit */
+                                                                "2a:eb:00", /* BIAC Carkit */
+                                                                "30:53:00", /* BIAC series */
+                                                                "00:17:53", /* ADAYO Carkit */
+                                                                "40:ef:4c", /* Road Rover Carkit */
+                                                                "00:07:04", /* Tiguan RNS315 */
+                                                               };
+    private static final String [] BlacklistDeviceForSendingVOIPCallIndsBackToBack =
+                                                               {"f4:15:fd"}; /* Rongwei 360 Car */
+    private static final String VOIP_CALL_NUMBER = "10000000";
+
+    //VR app launched successfully
+    private static final int VR_SUCCESS = 1;
+
+    //VR app failed to launch
+    private static final int VR_FAILURE = 0;
+
     private final BluetoothDevice mDevice;
 
+    // maintain call states in state machine as well
+    private final HeadsetCallState mStateMachineCallState =
+                 new HeadsetCallState(0, 0, 0, "", 0);
+
     // State machine states
     private final Disconnected mDisconnected = new Disconnected();
     private final Connecting mConnecting = new Connecting();
@@ -118,17 +172,33 @@
     private final AudioConnecting mAudioConnecting = new AudioConnecting();
     private final AudioDisconnecting mAudioDisconnecting = new AudioDisconnecting();
     private HeadsetStateBase mPrevState;
+    private HeadsetStateBase mCurrentState;
+
+    // used for synchronizing mCurrentState set/get
+    private final Object mLock = new Object();
 
     // Run time dependencies
     private final HeadsetService mHeadsetService;
     private final AdapterService mAdapterService;
     private final HeadsetNativeInterface mNativeInterface;
     private final HeadsetSystemInterface mSystemInterface;
+    private ConnectivityManager mConnectivityManager;
 
     // Runtime states
     private int mSpeakerVolume;
     private int mMicVolume;
     private HeadsetAgIndicatorEnableState mAgIndicatorEnableState;
+    private boolean mA2dpSuspend;
+    private boolean mIsCsCall = true;
+    private boolean mPendingScoForVR = false;
+    private boolean mIsCallIndDelay = false;
+    private boolean mIsBlacklistedDevice = false;
+    private int retryConnectCount = 0;
+    //ConcurrentLinkeQueue is used so that it is threadsafe
+    private ConcurrentLinkedQueue<HeadsetCallState> mPendingCallStates =
+                             new ConcurrentLinkedQueue<HeadsetCallState>();
+    private ConcurrentLinkedQueue<HeadsetCallState> mDelayedCSCallStates =
+                             new ConcurrentLinkedQueue<HeadsetCallState>();
     // The timestamp when the device entered connecting/connected state
     private long mConnectingTimestampMs = Long.MIN_VALUE;
     // Audio Parameters like NREC
@@ -138,9 +208,19 @@
     // HSP specific
     private boolean mNeedDialingOutReply;
 
+    // Hash for storing the A2DP connection states
+    private HashMap<BluetoothDevice, Integer> mA2dpConnState =
+                                          new HashMap<BluetoothDevice, Integer>();
+    // Hash for storing the A2DP play states
+    private HashMap<BluetoothDevice, Integer> mA2dpPlayState =
+                                          new HashMap<BluetoothDevice, Integer>();
+
     // Keys are AT commands, and values are the company IDs.
     private static final Map<String, Integer> VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID;
 
+    /* Retry outgoing connection after this time if the first attempt fails */
+    private static final int RETRY_CONNECT_TIME_SEC = 2500;
+
     static {
         VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID = new HashMap<>();
         VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
@@ -172,6 +252,8 @@
         mAdapterService = Objects.requireNonNull(adapterService, "AdapterService cannot be null");
         // Create phonebook helper
         mPhonebook = new AtPhonebook(mHeadsetService, mNativeInterface);
+        mConnectivityManager = (ConnectivityManager)
+                          mHeadsetService.getSystemService(mHeadsetService.CONNECTIVITY_SERVICE);
         // Initialize state machine
         addState(mDisconnected);
         addState(mConnecting);
@@ -181,6 +263,15 @@
         addState(mAudioConnecting);
         addState(mAudioDisconnecting);
         setInitialState(mDisconnected);
+
+        if (isDeviceBlacklistedForSendingCallIndsBackToBack()) {
+            CS_CALL_ALERTING_DELAY_TIME_MSEC = 0;
+            CS_CALL_ACTIVE_DELAY_TIME_MSEC = 0;
+            Log.w(TAG, "alerting delay " + CS_CALL_ALERTING_DELAY_TIME_MSEC +
+                      " active delay " + CS_CALL_ACTIVE_DELAY_TIME_MSEC);
+        }
+
+        Log.i(TAG," Exiting HeadsetStateMachine constructor for device :" + device);
     }
 
     static HeadsetStateMachine make(BluetoothDevice device, Looper looper,
@@ -189,6 +280,7 @@
         HeadsetStateMachine stateMachine =
                 new HeadsetStateMachine(device, looper, headsetService, adapterService,
                         nativeInterface, systemInterface);
+        Log.i(TAG," Starting StateMachine  device: " + device);
         stateMachine.start();
         Log.i(TAG, "Created state machine " + stateMachine + " for " + device);
         return stateMachine;
@@ -200,20 +292,39 @@
             Log.w(TAG, "destroy(), stateMachine is null");
             return;
         }
-        stateMachine.quitNow();
         stateMachine.cleanup();
+        stateMachine.quitNow();
     }
 
     public void cleanup() {
+        Log.i(TAG," destroy, current state " + getCurrentHeadsetStateMachineState());
+        if (getCurrentHeadsetStateMachineState() == mAudioOn) {
+            mAudioOn.broadcastAudioState(mDevice, BluetoothHeadset.STATE_AUDIO_CONNECTED,
+                                BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+            mAudioOn.broadcastConnectionState(mDevice, BluetoothProfile.STATE_CONNECTED,
+                                BluetoothProfile.STATE_DISCONNECTED);
+        }
+        if(getCurrentHeadsetStateMachineState() == mConnected){
+            mConnected.broadcastConnectionState(mDevice, BluetoothProfile.STATE_CONNECTED,
+                                     BluetoothProfile.STATE_DISCONNECTED);
+        }
+        if(getCurrentHeadsetStateMachineState() == mConnecting){
+            mConnecting.broadcastConnectionState(mDevice, BluetoothProfile.STATE_CONNECTING,
+                                     BluetoothProfile.STATE_DISCONNECTED);
+        }
         if (mPhonebook != null) {
             mPhonebook.cleanup();
         }
+        if (getAudioState() == BluetoothHeadset.STATE_AUDIO_CONNECTED &&
+                !mSystemInterface.getHeadsetPhoneState().getIsCsCall()) {
+            sendVoipConnectivityNetworktype(false);
+        }
         mAudioParams.clear();
     }
 
     public void dump(StringBuilder sb) {
         ProfileService.println(sb, "  mCurrentDevice: " + mDevice);
-        ProfileService.println(sb, "  mCurrentState: " + getCurrentState());
+        ProfileService.println(sb, "  mCurrentState: " + mCurrentState);
         ProfileService.println(sb, "  mPrevState: " + mPrevState);
         ProfileService.println(sb, "  mConnectionState: " + getConnectionState());
         ProfileService.println(sb, "  mAudioState: " + getAudioState());
@@ -249,6 +360,11 @@
                 throw new IllegalStateException("mPrevState is null on enter()");
             }
             enforceValidConnectionStateTransition();
+
+            synchronized(mLock) {
+                mCurrentState = this;
+                Log.e(TAG, "Setting mCurrentState as " + mCurrentState);
+            }
         }
 
         @Override
@@ -411,6 +527,13 @@
 
     }
 
+    public HeadsetStateBase getCurrentHeadsetStateMachineState() {
+        synchronized(mLock) {
+            Log.e(TAG, "returning mCurrentState as " + mCurrentState);
+            return mCurrentState;
+        }
+    }
+
     class Disconnected extends HeadsetStateBase {
         @Override
         int getConnectionStateInt() {
@@ -430,12 +553,21 @@
             updateAgIndicatorEnableState(null);
             mNeedDialingOutReply = false;
             mAudioParams.clear();
+
+            // reset call information
+            mStateMachineCallState.mNumActive = 0;
+            mStateMachineCallState.mNumHeld = 0;
+            mStateMachineCallState.mCallState = 0;
+            mStateMachineCallState.mNumber = "";
+            mStateMachineCallState.mType = 0;
+
             broadcastStateTransitions();
             // Remove the state machine for unbonded devices
             if (mPrevState != null
                     && mAdapterService.getBondState(mDevice) == BluetoothDevice.BOND_NONE) {
                 getHandler().post(() -> mHeadsetService.removeStateMachine(mDevice));
             }
+            mIsBlacklistedDevice = false;
         }
 
         @Override
@@ -449,6 +581,13 @@
                                 "CONNECT failed, device=" + device + ", currentDevice=" + mDevice);
                         break;
                     }
+
+                    stateLogD(" retryConnectCount = " + retryConnectCount);
+                    if (retryConnectCount >= MAX_RETRY_CONNECT_COUNT) {
+                        // max attempts reached, reset it to 0
+                        retryConnectCount = 0;
+                        break;
+                    }
                     if (!mNativeInterface.connectHfp(device)) {
                         stateLogE("CONNECT failed for connectHfp(" + device + ")");
                         // No state transition is involved, fire broadcast immediately
@@ -456,6 +595,7 @@
                                 BluetoothProfile.STATE_DISCONNECTED);
                         break;
                     }
+                    retryConnectCount++;
                     transitionTo(mConnecting);
                     break;
                 case DISCONNECT:
@@ -467,6 +607,10 @@
                 case DEVICE_STATE_CHANGED:
                     stateLogD("Ignoring DEVICE_STATE_CHANGED event");
                     break;
+                case UPDATE_CALL_TYPE:
+                    stateLogD("UPDATE_CALL_TYPE event");
+                    processIntentUpdateCallType((Intent) message.obj);
+                    break;
                 case STACK_EVENT:
                     HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
                     stateLogD("STACK_EVENT: " + event);
@@ -545,6 +689,19 @@
             super.enter();
             mConnectingTimestampMs = SystemClock.uptimeMillis();
             sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs);
+            mSystemInterface.queryPhoneState();
+            // update call states in StateMachine
+            mStateMachineCallState.mNumActive =
+                   mSystemInterface.getHeadsetPhoneState().getNumActiveCall();
+            mStateMachineCallState.mNumHeld =
+                   mSystemInterface.getHeadsetPhoneState().getNumHeldCall();
+            mStateMachineCallState.mCallState =
+                   mSystemInterface.getHeadsetPhoneState().getCallState();
+            mStateMachineCallState.mNumber =
+                   mSystemInterface.getHeadsetPhoneState().getNumber();
+            mStateMachineCallState.mType =
+                   mSystemInterface.getHeadsetPhoneState().getType();
+
             broadcastStateTransitions();
         }
 
@@ -567,12 +724,27 @@
                     transitionTo(mDisconnected);
                     break;
                 }
-                case CALL_STATE_CHANGED:
-                    stateLogD("ignoring CALL_STATE_CHANGED event");
-                    break;
                 case DEVICE_STATE_CHANGED:
                     stateLogD("ignoring DEVICE_STATE_CHANGED event");
                     break;
+                case A2DP_STATE_CHANGED:
+                    stateLogD("A2DP_STATE_CHANGED event");
+                    processIntentA2dpPlayStateChanged(message.arg1);
+                    break;
+                case CALL_STATE_CHANGED: {
+                     HeadsetCallState callState = (HeadsetCallState) message.obj;
+                     processCallState(callState, false);
+                     break;
+                }
+                case UPDATE_CALL_TYPE:
+                    stateLogD("UPDATE_CALL_TYPE event");
+                    processIntentUpdateCallType((Intent) message.obj);
+                    break;
+                case RESUME_A2DP: {
+                     stateLogD("RESUME_A2DP evt, resuming A2DP");
+                     mHeadsetService.getHfpA2DPSyncInterface().releaseA2DP(mDevice);
+                     break;
+                }
                 case STACK_EVENT:
                     HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
                     stateLogD("STACK_EVENT: " + event);
@@ -617,8 +789,7 @@
                             processAtCops(event.device);
                             break;
                         case HeadsetStackEvent.EVENT_TYPE_AT_CLCC:
-                            Log.w(TAG, "Connecting: Unexpected CLCC event for" + event.device);
-                            processAtClcc(event.device);
+                            stateLogW("Connecting: Unexpected CLCC event for" + event.device);
                             break;
                         case HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT:
                             stateLogW("Unexpected unknown AT event for" + event.device + ", cmd="
@@ -664,6 +835,15 @@
             switch (state) {
                 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
                     stateLogW("Disconnected");
+                    processWBSEvent(HeadsetHalConstants.BTHF_WBS_NO);
+                    stateLogD(" retryConnectCount = " + retryConnectCount);
+                    if(retryConnectCount == 1) {
+                        Log.d(TAG," retry once more ");
+                        sendMessageDelayed(CONNECT, mDevice, RETRY_CONNECT_TIME_SEC);
+                    } else if (retryConnectCount >= MAX_RETRY_CONNECT_COUNT) {
+                        // we already tried twice.
+                        retryConnectCount = 0;
+                    }
                     transitionTo(mDisconnected);
                     break;
                 case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
@@ -671,6 +851,7 @@
                     break;
                 case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
                     stateLogD("SLC connected");
+                    retryConnectCount = 0;
                     transitionTo(mConnected);
                     break;
                 case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
@@ -814,6 +995,15 @@
                         stateLogW("Failed to start voice recognition");
                         break;
                     }
+
+                    if (mHeadsetService.getHfpA2DPSyncInterface().suspendA2DP(
+                          HeadsetA2dpSync.A2DP_SUSPENDED_BY_VR, mDevice) == true) {
+                       Log.d(TAG, "mesg VOICE_RECOGNITION_START: A2DP is playing,"+
+                             " return and establish SCO after A2DP supended");
+                        break;
+                    }
+                    // create SCO since there is no A2DP playback
+                    mNativeInterface.connectAudio(mDevice);
                     break;
                 }
                 case VOICE_RECOGNITION_STOP: {
@@ -830,13 +1020,63 @@
                     break;
                 }
                 case CALL_STATE_CHANGED: {
+                    boolean isPts = SystemProperties.getBoolean("vendor.bt.pts.certification", false);
                     HeadsetCallState callState = (HeadsetCallState) message.obj;
-                    if (!mNativeInterface.phoneStateChange(mDevice, callState)) {
-                        stateLogW("processCallState: failed to update call state " + callState);
-                        break;
+                    // for PTS, send the indicators as is
+                    if (isPts) {
+                        if (!mNativeInterface.phoneStateChange(mDevice, callState)) {
+                            stateLogW("processCallState: failed to update call state " + callState);
+                            break;
+                        }
+                    }
+                    else
+                        processCallStatesDelayed(callState, false);
+                    break;
+                }
+                case CS_CALL_STATE_CHANGED_ALERTING: {
+                    // get the top of the Q
+                    HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
+                    // top of the queue is call alerting
+                    if(tempCallState != null &&
+                        tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING)
+                    {
+                        stateLogD("alerting message timer expired, send alerting update");
+                        //dequeue the alerting call state;
+                        mDelayedCSCallStates.poll();
+                        processCallState(tempCallState, false);
+                    }
+
+                    // top of the queue == call active
+                    tempCallState = mDelayedCSCallStates.peek();
+                    if (tempCallState != null &&
+                         tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_IDLE)
+                    {
+                        stateLogD("alerting message timer expired, send delayed active mesg");
+                        //send delayed message for call active;
+                        Message msg = obtainMessage(CS_CALL_STATE_CHANGED_ACTIVE);
+                        msg.arg1 = 0;
+                        sendMessageDelayed(msg, CS_CALL_ACTIVE_DELAY_TIME_MSEC);
                     }
                     break;
                 }
+                case CS_CALL_STATE_CHANGED_ACTIVE: {
+                    // get the top of the Q
+                    // top of the queue == call active
+                    HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
+                    if (tempCallState != null &&
+                         tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_IDLE)
+                    {
+                        stateLogD("active message timer expired, send active update");
+                        //dequeue the active call state;
+                        mDelayedCSCallStates.poll();
+                        processCallState(tempCallState, false);
+                    }
+                }
+                    break;
+                case A2DP_STATE_CHANGED:
+                    stateLogD("A2DP_STATE_CHANGED event");
+                    processIntentA2dpPlayStateChanged(message.arg1);
+                    break;
                 case DEVICE_STATE_CHANGED:
                     mNativeInterface.notifyDeviceStatus(mDevice, (HeadsetDeviceState) message.obj);
                     break;
@@ -867,8 +1107,22 @@
                         break;
                     }
                     mNativeInterface.atResponseCode(mDevice,
-                            message.arg1 == 1 ? HeadsetHalConstants.AT_RESPONSE_OK
+                            message.arg1 == VR_SUCCESS ? HeadsetHalConstants.AT_RESPONSE_OK
                                     : HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+                    if (message.arg1 != VR_SUCCESS) {
+                       Log.d(TAG, "VOICE_RECOGNITION_RESULT: not creating SCO since VR app"+
+                                  " failed to start VR");
+                       break;
+                    }
+
+                    if (mHeadsetService.getHfpA2DPSyncInterface().suspendA2DP(
+                          HeadsetA2dpSync.A2DP_SUSPENDED_BY_VR, mDevice) == true) {
+                       Log.d(TAG, "mesg VOICE_RECOGNITION_START: A2DP is playing,"+
+                             " return and establish SCO after A2DP supended");
+                        break;
+                    }
+                    // create SCO since there is no A2DP playback
+                    mNativeInterface.connectAudio(mDevice);
                     break;
                 }
                 case DIALING_OUT_RESULT: {
@@ -888,6 +1142,25 @@
                 case INTENT_CONNECTION_ACCESS_REPLY:
                     handleAccessPermissionResult((Intent) message.obj);
                     break;
+                case PROCESS_CPBR:
+                    Intent intent = (Intent) message.obj;
+                    processCpbr(intent);
+                    break;
+                case SEND_INCOMING_CALL_IND:
+                    HeadsetCallState callState =
+                        new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING,
+                                 mSystemInterface.getHeadsetPhoneState().getNumber(),
+                                 mSystemInterface.getHeadsetPhoneState().getType());
+                    mNativeInterface.phoneStateChange(mDevice, callState);
+                    break;
+                case QUERY_PHONE_STATE_AT_SLC:
+                    stateLogD("Update call states after SLC is up");
+                    mSystemInterface.queryPhoneState();
+                    break;
+                case UPDATE_CALL_TYPE:
+                    stateLogD("UPDATE_CALL_TYPE event");
+                    processIntentUpdateCallType((Intent) message.obj);
+                    break;
                 case STACK_EVENT:
                     HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
                     stateLogD("STACK_EVENT: " + event);
@@ -979,6 +1252,7 @@
                     break;
                 case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
                     stateLogE("processConnectionEvent: SLC connected again, shouldn't happen");
+                    retryConnectCount = 0;
                     break;
                 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
                     stateLogI("processConnectionEvent: Disconnecting");
@@ -986,6 +1260,7 @@
                     break;
                 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
                     stateLogI("processConnectionEvent: Disconnected");
+                    processWBSEvent(HeadsetHalConstants.BTHF_WBS_NO);
                     transitionTo(mDisconnected);
                     break;
                 default:
@@ -1020,12 +1295,28 @@
                 // Reset NREC on connect event. Headset will override later
                 processNoiseReductionEvent(true);
                 // Query phone state for initial setup
-                mSystemInterface.queryPhoneState();
+                sendMessageDelayed(QUERY_PHONE_STATE_AT_SLC, QUERY_PHONE_STATE_CHANGED_DELAYED);
+                // Checking for the Blacklisted device Addresses
+                mIsBlacklistedDevice = isConnectedDeviceBlacklistedforIncomingCall();
+                if (mSystemInterface.isInCall() || mSystemInterface.isRinging()) {
+                   stateLogW("Connected: enter: suspending A2DP for Call since SLC connected");
+                   // suspend A2DP since call is there
+                   mHeadsetService.getHfpA2DPSyncInterface().suspendA2DP(
+                           HeadsetA2dpSync.A2DP_SUSPENDED_BY_CS_CALL, mDevice);
+                }
                 // Remove pending connection attempts that were deferred during the pending
                 // state. This is to prevent auto connect attempts from disconnecting
                 // devices that previously successfully connected.
                 removeDeferredMessages(CONNECT);
             }
+            if ((mPrevState == mAudioOn) || (mPrevState == mAudioDisconnecting)||
+                 (mPrevState == mAudioConnecting)) {
+                if (!(mSystemInterface.isInCall() || mSystemInterface.isRinging())) {
+                        // SCO disconnected, resume A2DP if there is no call
+                        stateLogD("SCO disconnected, set A2DPsuspended to false");
+                        mHeadsetService.getHfpA2DPSyncInterface().releaseA2DP(mDevice);
+                }
+            }
             broadcastStateTransitions();
         }
 
@@ -1056,9 +1347,13 @@
                 break;
                 case CONNECT_AUDIO:
                     stateLogD("CONNECT_AUDIO, device=" + mDevice);
-                    mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
+                    int a2dpState = mHeadsetService.getHfpA2DPSyncInterface().isA2dpPlaying();
+                    if (!mHeadsetService.isScoAcceptable(mDevice)|| (a2dpState == HeadsetA2dpSync.A2DP_PLAYING)) {
+                        stateLogW("No Active/Held call, no call setup,and no in-band ringing,"
+                                  + " or A2Dp is playing, not allowing SCO, device=" + mDevice);
+                        break;
+                    }
                     if (!mNativeInterface.connectAudio(mDevice)) {
-                        mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
                         stateLogE("Failed to connect SCO audio for " + mDevice);
                         // No state change involved, fire broadcast immediately
                         broadcastAudioState(mDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
@@ -1071,6 +1366,11 @@
                     stateLogD("ignore DISCONNECT_AUDIO, device=" + mDevice);
                     // ignore
                     break;
+                case RESUME_A2DP: {
+                     stateLogD("RESUME_A2DP evt, resuming A2DP");
+                     mHeadsetService.getHfpA2DPSyncInterface().releaseA2DP(mDevice);
+                     break;
+                }
                 default:
                     return super.processMessage(message);
             }
@@ -1092,6 +1392,12 @@
                                 BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                         break;
                     }
+                    if (!mSystemInterface.getHeadsetPhoneState().getIsCsCall()) {
+                        stateLogI("Sco connected for call other than CS, check network type");
+                        sendVoipConnectivityNetworktype(true);
+                    } else {
+                        stateLogI("Sco connected for CS call, do not check network type");
+                    }
                     stateLogI("processAudioEvent: audio connected");
                     transitionTo(mAudioOn);
                     break;
@@ -1110,6 +1416,12 @@
                     transitionTo(mAudioConnecting);
                     break;
                 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
+                    if (!(mSystemInterface.isInCall() || mSystemInterface.isRinging())) {
+                        // SCO disconnected, resume A2DP if there is no call
+                        stateLogD("SCO disconnected, set A2DPsuspended to false");
+                        mHeadsetService.getHfpA2DPSyncInterface().releaseA2DP(mDevice);
+                    }
+                    break;
                 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
                     // ignore
                     break;
@@ -1205,7 +1517,14 @@
                     && !hasDeferredMessages(DISCONNECT_AUDIO)) {
                 mHeadsetService.setActiveDevice(mDevice);
             }
-            setAudioParameters();
+            // If current device is TWSPLUS device and peer TWSPLUS device is already
+            // has SCO, dont need to update teh Audio Manager
+            if (mAdapterService.isTwsPlusDevice(mDevice) &&
+               mHeadsetService.isAudioConnected(mHeadsetService.getTwsPlusConnectedPeer(mDevice))) {
+               stateLogW("Dont update Audio as this TWS peer eSCO");
+            } else {
+               setAudioParameters();
+            }
             broadcastStateTransitions();
         }
 
@@ -1224,14 +1543,24 @@
                         stateLogW("DISCONNECT, device " + device + " not connected");
                         break;
                     }
-                    // Disconnect BT SCO first
-                    if (!mNativeInterface.disconnectAudio(mDevice)) {
-                        stateLogW("DISCONNECT failed, device=" + mDevice);
-                        // if disconnect BT SCO failed, transition to mConnected state to force
-                        // disconnect device
+                    if (mAdapterService.isTwsPlusDevice(device)) {
+                        //for twsplus device, don't disconnect the SCO for app
+                        //force the disocnnect of HF and in turn let SCO shutdown
+                        //so that SCO SM handles it gracefully
+                        if (!mNativeInterface.disconnectHfp(device)) {
+                            stateLogW("DISCONNECT failed TWS case, device=" + mDevice);
+                        }
+                        transitionTo(mDisconnecting);
+                    } else {
+                        // Disconnect BT SCO first
+                        if (!mNativeInterface.disconnectAudio(mDevice)) {
+                            stateLogW("DISCONNECT failed, device=" + mDevice);
+                            // if disconnect BT SCO failed, transition to mConnected state to force
+                            // disconnect device
+                        }
+                        deferMessage(obtainMessage(DISCONNECT, mDevice));
+                        transitionTo(mAudioDisconnecting);
                     }
-                    deferMessage(obtainMessage(DISCONNECT, mDevice));
-                    transitionTo(mAudioDisconnecting);
                     break;
                 }
                 case CONNECT_AUDIO: {
@@ -1291,6 +1620,22 @@
             switch (state) {
                 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
                     stateLogI("processAudioEvent: audio disconnected by remote");
+                    if (mAdapterService.isTwsPlusDevice(mDevice) && mHeadsetService.isAudioOn()) {
+                        //If It is TWSP device, make sure SCO is not active on
+                        //any devices before letting Audio knowing about it
+                        stateLogI("TWS+ device and other SCO is still Active, no BT_SCO=off");
+                    } else {
+                        if(mSystemInterface.getAudioManager().isSpeakerphoneOn()) {
+                            mSystemInterface.getAudioManager().setSpeakerphoneOn(true);
+                        }
+                    }
+                    if (!mSystemInterface.getHeadsetPhoneState().getIsCsCall()) {
+                        stateLogI("Sco disconnected for call other than CS, check network type");
+                        sendVoipConnectivityNetworktype(false);
+                        mSystemInterface.getHeadsetPhoneState().setIsCsCall(true);
+                    } else {
+                        stateLogI("Sco disconnected for CS call, do not check network type");
+                    }
                     transitionTo(mConnected);
                     break;
                 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
@@ -1305,10 +1650,11 @@
 
         private void processIntentScoVolume(Intent intent, BluetoothDevice device) {
             int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
+            stateLogD(" mSpeakerVolume = " + mSpeakerVolume + " volValue = " + volumeValue);
             if (mSpeakerVolume != volumeValue) {
                 mSpeakerVolume = volumeValue;
                 mNativeInterface.setVolume(device, HeadsetHalConstants.VOLUME_TYPE_SPK,
-                        mSpeakerVolume);
+                    mSpeakerVolume);
             }
         }
     }
@@ -1357,6 +1703,15 @@
             switch (state) {
                 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
                     stateLogI("processAudioEvent: audio disconnected");
+                    if (mAdapterService.isTwsPlusDevice(mDevice) && mHeadsetService.isAudioOn()) {
+                         //If It is TWSP device, make sure SCO is not active on
+                         //any devices before letting Audio knowing about it
+                         stateLogI("TWS+ device and other SCO is still Active, no BT_SCO=off");
+                    } else {
+                        if(mSystemInterface.getAudioManager().isSpeakerphoneOn()) {
+                            mSystemInterface.getAudioManager().setSpeakerphoneOn(true);
+                        }
+                    }
                     transitionTo(mConnected);
                     break;
                 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
@@ -1401,7 +1756,8 @@
      */
     @VisibleForTesting
     public synchronized int getConnectionState() {
-        HeadsetStateBase state = (HeadsetStateBase) getCurrentState();
+        //getCurrentState()
+        HeadsetStateBase state = (HeadsetStateBase) getCurrentHeadsetStateMachineState();
         if (state == null) {
             return BluetoothHeadset.STATE_DISCONNECTED;
         }
@@ -1416,7 +1772,8 @@
      * {@link BluetoothHeadset#STATE_AUDIO_CONNECTED}
      */
     public synchronized int getAudioState() {
-        HeadsetStateBase state = (HeadsetStateBase) getCurrentState();
+        //getCurrentState()
+        HeadsetStateBase state = (HeadsetStateBase) getCurrentHeadsetStateMachineState();
         if (state == null) {
             return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
         }
@@ -1427,6 +1784,14 @@
         return mConnectingTimestampMs;
     }
 
+    public boolean isPendingRetryConnect() {
+        if((getConnectionState() == BluetoothHeadset.STATE_DISCONNECTED) &&
+            (retryConnectCount > 0) && hasMessages(CONNECT)) {
+            return true;
+        }
+        return false;
+    }
+
     /*
      * Put the AT command, company ID, arguments, and device in an Intent and broadcast it.
      */
@@ -1505,8 +1870,9 @@
         }
         if ((number == null) || (number.length() == 0)) {
             dialNumber = mPhonebook.getLastDialledNumber();
-            if (dialNumber == null) {
-                Log.w(TAG, "processDialCall, last dial number null");
+            log("dialNumber: " + dialNumber);
+            if ((dialNumber == null) || (dialNumber.length() == 0)) {
+                Log.w(TAG, "processDialCall, last dial number null or empty ");
                 mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                 return;
             }
@@ -1558,13 +1924,16 @@
 
     private void processVolumeEvent(int volumeType, int volume) {
         // Only current active device can change SCO volume
-        if (!mDevice.equals(mHeadsetService.getActiveDevice())) {
+        AdapterService adapterService = AdapterService.getAdapterService();
+        if (!adapterService.isTwsPlusDevice(mDevice) &&
+            !mDevice.equals(mHeadsetService.getActiveDevice())) {
             Log.w(TAG, "processVolumeEvent, ignored because " + mDevice + " is not active");
             return;
         }
         if (volumeType == HeadsetHalConstants.VOLUME_TYPE_SPK) {
             mSpeakerVolume = volume;
-            int flag = (getCurrentState() == mAudioOn) ? AudioManager.FLAG_SHOW_UI : 0;
+            int flag = (getCurrentHeadsetStateMachineState() == mAudioOn)
+                        ? AudioManager.FLAG_SHOW_UI : 0;
             mSystemInterface.getAudioManager()
                     .setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, flag);
         } else if (volumeType == HeadsetHalConstants.VOLUME_TYPE_MIC) {
@@ -1575,6 +1944,279 @@
         }
     }
 
+    private void processCallStatesDelayed(HeadsetCallState callState, boolean isVirtualCall)
+    {
+        log("Enter processCallStatesDelayed");
+        final HeadsetPhoneState mPhoneState = mSystemInterface.getHeadsetPhoneState();
+        if (callState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING)
+        {
+            // at this point, queue should be empty.
+            processCallState(callState, false);
+        }
+        // update is for call alerting
+        else if (callState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING &&
+                  mStateMachineCallState.mNumActive == callState.mNumActive &&
+                  mStateMachineCallState.mNumHeld == callState.mNumHeld &&
+                  mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING)
+        {
+            log("Queue alerting update, send alerting delayed mesg");
+            //Q the call state;
+            mDelayedCSCallStates.add(callState);
+
+            //send delayed message for call alerting;
+            Message msg = obtainMessage(CS_CALL_STATE_CHANGED_ALERTING);
+            msg.arg1 = 0;
+            sendMessageDelayed(msg, CS_CALL_ALERTING_DELAY_TIME_MSEC);
+        }
+        // call moved to active from alerting state
+        else if (mStateMachineCallState.mNumActive == 0 &&
+                 callState.mNumActive == 1 &&
+                 mStateMachineCallState.mNumHeld == callState.mNumHeld &&
+                 (mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING ||
+                  mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING ))
+        {
+            log("Call moved to active state from alerting");
+            // get the top of the Q
+            HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
+
+            //if (top of the Q == alerting)
+            if( tempCallState != null &&
+                 tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING)
+            {
+                log("Call is active, Queue it, top of Queue is alerting");
+                //Q active update;
+                mDelayedCSCallStates.add(callState);
+            }
+            else
+            // Q is empty
+            {
+                log("is Q empty " + mDelayedCSCallStates.isEmpty());
+                log("Call is active, Queue it, send delayed active mesg");
+                //Q active update;
+                mDelayedCSCallStates.add(callState);
+                //send delayed message for call active;
+                Message msg = obtainMessage(CS_CALL_STATE_CHANGED_ACTIVE);
+                msg.arg1 = 0;
+                sendMessageDelayed(msg, CS_CALL_ACTIVE_DELAY_TIME_MSEC);
+            }
+        }
+        // call setup or call ended
+        else if((mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING ||
+                  mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING ) &&
+                  callState.mCallState == HeadsetHalConstants.CALL_STATE_IDLE &&
+                  mStateMachineCallState.mNumActive == callState.mNumActive &&
+                  mStateMachineCallState.mNumHeld == callState.mNumHeld)
+        {
+            log("call setup or call is ended");
+            // get the top of the Q
+            HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
+
+            //if (top of the Q == alerting)
+            if(tempCallState != null &&
+                tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING)
+            {
+                log("Call is ended, remove delayed alerting mesg");
+                removeMessages(CS_CALL_STATE_CHANGED_ALERTING);
+                //DeQ(alerting);
+                mDelayedCSCallStates.poll();
+                // send 2,3 although the call is ended to make sure that we are sending 2,3 always
+                processCallState(tempCallState, false);
+
+                // update the top of the Q entry so that we process the active
+                // call entry from the Q below
+                tempCallState = mDelayedCSCallStates.peek();
+            }
+
+            //if (top of the Q == active)
+            if (tempCallState != null &&
+                 tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_IDLE)
+            {
+                log("Call is ended, remove delayed active mesg");
+                removeMessages(CS_CALL_STATE_CHANGED_ACTIVE);
+                //DeQ(active);
+                mDelayedCSCallStates.poll();
+            }
+            // send current call state which will take care of sending call end indicator
+            processCallState(callState, false);
+        } else {
+            HeadsetCallState tempCallState;
+
+            // if there are pending call states to be sent, send them now
+            if (mDelayedCSCallStates.isEmpty() != true)
+            {
+                log("new call update, removing pending alerting, active messages");
+                // remove pending delayed call states
+                removeMessages(CS_CALL_STATE_CHANGED_ALERTING);
+                removeMessages(CS_CALL_STATE_CHANGED_ACTIVE);
+            }
+
+            while (mDelayedCSCallStates.isEmpty() != true)
+            {
+                tempCallState = mDelayedCSCallStates.poll();
+                if (tempCallState != null)
+                {
+                    processCallState(tempCallState, false);
+                }
+            }
+            // it is incoming call or MO call in non-alerting, non-active state.
+            processCallState(callState, isVirtualCall);
+        }
+        log("Exit processCallStatesDelayed");
+    }
+
+    private void processCallState(HeadsetCallState callState, boolean isVirtualCall) {
+        /* If active call is ended, no held call is present, disconnect SCO
+         * and fake the MT Call indicators. */
+        boolean isPts =
+                SystemProperties.getBoolean("vendor.bt.pts.certification", false);
+        if (!isPts) {
+            log("mIsBlacklistedDevice:" + mIsBlacklistedDevice);
+            if (mIsBlacklistedDevice &&
+                mStateMachineCallState.mNumActive == 1 &&
+                callState.mNumActive == 0 &&
+                callState.mNumHeld == 0 &&
+                callState.mCallState == HeadsetHalConstants.CALL_STATE_INCOMING) {
+
+                log("Disconnect SCO since active call is ended," +
+                                    "only waiting call is there");
+                Message m = obtainMessage(DISCONNECT_AUDIO);
+                m.obj = mDevice;
+                sendMessage(m);
+
+                log("Send Idle call indicators once Active call disconnected.");
+                // TODO: cross check this
+                mStateMachineCallState.mCallState = 
+                                               HeadsetHalConstants.CALL_STATE_IDLE;
+                HeadsetCallState updateCallState = new HeadsetCallState(callState.mNumActive,
+                                 callState.mNumHeld,
+                                 HeadsetHalConstants.CALL_STATE_IDLE,
+                                 callState.mNumber,
+                                 callState.mType);
+                mNativeInterface.phoneStateChange(mDevice, updateCallState);
+                mIsCallIndDelay = true;
+            }
+        }
+        mStateMachineCallState.mNumActive = callState.mNumActive;
+        mStateMachineCallState.mNumHeld = callState.mNumHeld;
+        // get the top of the Q
+        HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
+
+        if ( !isVirtualCall && tempCallState != null &&
+             tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING &&
+             callState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING) {
+             log("update call state as dialing since alerting update is in Q");
+             log("current call state is " + mStateMachineCallState.mCallState);
+             callState.mCallState = HeadsetHalConstants.CALL_STATE_DIALING;
+        }
+
+        mStateMachineCallState.mCallState = callState.mCallState;
+        mStateMachineCallState.mNumber = callState.mNumber;
+        mStateMachineCallState.mType = callState.mType;
+
+        log("processCallState: mNumActive: " + callState.mNumActive + " mNumHeld: "
+                + callState.mNumHeld + " mCallState: " + callState.mCallState);
+        log("processCallState: mNumber: " + callState.mNumber + " mType: " + callState.mType);
+
+        processA2dpState(callState);
+    }
+
+    /* This function makes sure that we send a2dp suspend before updating on Incomming call status.
+       There may problem with some headsets if send ring and a2dp is not suspended,
+       so here we suspend stream if active before updating remote.We resume streaming once
+       callstate is idle and there are no active or held calls. */
+
+    private void processA2dpState(HeadsetCallState callState) {
+        int a2dpState = mHeadsetService.getHfpA2DPSyncInterface().isA2dpPlaying();
+        Log.d(TAG, "processA2dpState: isA2dpPlaying() " + a2dpState);
+
+        if ((mSystemInterface.isInCall() || mSystemInterface.isRinging()) &&
+              getConnectionState() == BluetoothHeadset.STATE_CONNECTED) {
+            // if A2DP is playing, add CS call states and return
+            if (mHeadsetService.getHfpA2DPSyncInterface().suspendA2DP(
+                 HeadsetA2dpSync.A2DP_SUSPENDED_BY_CS_CALL, mDevice) == true) {
+                 Log.d(TAG, "processA2dpState: A2DP is playing, suspending it,"+
+                             "cache the call state for future");
+                 mPendingCallStates.add(callState);
+                 return;
+            }
+        }
+
+        if (getCurrentHeadsetStateMachineState() != mDisconnected) {
+            log("No A2dp playing to suspend, mIsCallIndDelay: " + mIsCallIndDelay +
+                " mPendingCallStates.size(): " + mPendingCallStates.size());
+            //When MO call creation and disconnection done back to back, Make sure to send
+            //the call indicators in a sequential way to remote
+            if (mPendingCallStates.size() != 0) {
+                Log.d(TAG, "Cache the call state, PendingCallStates list is not empty");
+                mPendingCallStates.add(callState);
+                return;
+            }
+            if (mIsCallIndDelay) {
+                mIsCallIndDelay = false;
+                sendMessageDelayed(SEND_INCOMING_CALL_IND, INCOMING_CALL_IND_DELAY);
+            } else {
+                mNativeInterface.phoneStateChange(mDevice, callState);
+            }
+        }
+    }
+
+    private void processIntentUpdateCallType(Intent intent) {
+        mIsCsCall = intent.getBooleanExtra(TelecomManager.EXTRA_CALL_TYPE_CS, true);
+        Log.d(TAG, "processIntentUpdateCallType " + mIsCsCall);
+        final HeadsetPhoneState mPhoneState = mSystemInterface.getHeadsetPhoneState();
+        mPhoneState.setIsCsCall(mIsCsCall);
+        if (getAudioState() == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+            if (!mPhoneState.getIsCsCall()) {
+                log("processIntentUpdateCallType, Non CS call, check for network type");
+                sendVoipConnectivityNetworktype(true);
+            } else {
+                log("processIntentUpdateCallType, CS call, do not check for network type");
+            }
+        } else {
+            log("processIntentUpdateCallType: Sco not yet connected");
+        }
+    }
+
+    private void processIntentA2dpPlayStateChanged(int a2dpState) {
+        Log.d(TAG, "Enter processIntentA2dpPlayStateChanged(): a2dp state "+
+                  a2dpState);
+        if (mHeadsetService.isVRStarted()) {
+            Log.d(TAG, "VR is in started state");
+            if (mDevice.equals(mHeadsetService.getActiveDevice())) {
+               Log.d(TAG, "creating SCO for " + mDevice);
+               mNativeInterface.connectAudio(mDevice);
+            }
+        } else if (mSystemInterface.isInCall() || mHeadsetService.isVirtualCallStarted()){
+            //send incoming phone status to remote device
+            Log.d(TAG, "A2dp is suspended, updating phone states");
+            Iterator<HeadsetCallState> it = mPendingCallStates.iterator();
+            if (it != null) {
+               while (it.hasNext()) {
+                  HeadsetCallState callState = it.next();
+                  Log.d(TAG, "mIsCallIndDelay: " + mIsCallIndDelay);
+                  mNativeInterface.phoneStateChange(mDevice, callState);
+                  it.remove();
+               }
+            } else {
+               Log.d(TAG, "There are no pending call state changes");
+            }
+        } else {
+            Log.d(TAG, "A2DP suspended when there is no CS/VOIP calls or VR, resuming A2DP");
+            //When A2DP is suspended and the call is terminated,
+            //clean up the PendingCallStates list
+            Iterator<HeadsetCallState> it = mPendingCallStates.iterator();
+            if (it != null) {
+               while (it.hasNext()) {
+                  HeadsetCallState callState = it.next();
+                  mNativeInterface.phoneStateChange(mDevice, callState);
+                  it.remove();
+               }
+            }
+            mHeadsetService.getHfpA2DPSyncInterface().releaseA2DP(mDevice);
+        }
+        Log.d(TAG, "Exit processIntentA2dpPlayStateChanged()");
+    }
+
     private void processNoiseReductionEvent(boolean enable) {
         String prevNrec = mAudioParams.getOrDefault(HEADSET_NREC, HEADSET_AUDIO_FEATURE_OFF);
         String newNrec = enable ? HEADSET_AUDIO_FEATURE_ON : HEADSET_AUDIO_FEATURE_OFF;
@@ -1625,30 +2267,57 @@
     }
 
     private void processAtCind(BluetoothDevice device) {
-        int call, callSetup;
+        int call, callSetup, call_state, service, signal;
+         // get the top of the Q 
+        HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
         final HeadsetPhoneState phoneState = mSystemInterface.getHeadsetPhoneState();
 
         /* Handsfree carkits expect that +CIND is properly responded to
          Hence we ensure that a proper response is sent
          for the virtual call too.*/
         if (mHeadsetService.isVirtualCallStarted()) {
-            call = 1;
+            call = mStateMachineCallState.mNumActive;
             callSetup = 0;
         } else {
             // regular phone call
-            call = phoneState.getNumActiveCall();
-            callSetup = phoneState.getNumHeldCall();
+            call = mStateMachineCallState.mNumActive;
+            callSetup = mStateMachineCallState.mNumHeld;
+        }
+        if(tempCallState != null &&
+            tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING)
+              call_state = HeadsetHalConstants.CALL_STATE_DIALING;
+        else
+              call_state = mStateMachineCallState.mCallState;
+        log("sending call state in CIND resp as " + call_state);
+
+        /* Some Handsfree devices or carkits expect the +CIND to be properly
+           responded with the correct service availablity and signal strength,
+           while the regular call is active or held or in progress.*/
+         if(((!mHeadsetService.isVirtualCallStarted()) &&
+            (mStateMachineCallState.mNumActive > 0) || (mStateMachineCallState.mNumHeld > 0) ||
+             mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING ||
+             mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING ||
+             mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_INCOMING) &&
+            (phoneState.getCindService() == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE)) {
+             log("processAtCind: If regular call is in process/active/held while RD connection " +
+                   "during BT-ON, update service availablity and signal strength");
+             service = HeadsetHalConstants.NETWORK_STATE_AVAILABLE;
+             signal = 3;
+         } else {
+             service = phoneState.getCindService();
+             signal = phoneState.getCindSignal();
         }
 
-        mNativeInterface.cindResponse(device, phoneState.getCindService(), call, callSetup,
-                phoneState.getCallState(), phoneState.getCindSignal(), phoneState.getCindRoam(),
+        mNativeInterface.cindResponse(device, service, call, callSetup,
+                call_state, signal, phoneState.getCindRoam(),
                 phoneState.getCindBatteryCharge());
+        log("Exit processAtCind()");
     }
 
     private void processAtCops(BluetoothDevice device) {
         String operatorName = mSystemInterface.getNetworkOperator();
-        if (operatorName == null) {
-            operatorName = "";
+        if (operatorName == null || operatorName.equals("")) {
+            operatorName = "No operator";
         }
         mNativeInterface.copsResponse(device, operatorName);
     }
@@ -1656,12 +2325,21 @@
     private void processAtClcc(BluetoothDevice device) {
         if (mHeadsetService.isVirtualCallStarted()) {
             // In virtual call, send our phone number instead of remote phone number
-            String phoneNumber = mSystemInterface.getSubscriberNumber();
+            // some carkits cross-check subscriber number( fetched by AT+CNUM) against
+            // number sent in clcc and reject sco connection.
+            String phoneNumber = VOIP_CALL_NUMBER;
             if (phoneNumber == null) {
                 phoneNumber = "";
             }
             int type = PhoneNumberUtils.toaFromString(phoneNumber);
-            mNativeInterface.clccResponse(device, 1, 0, 0, 0, false, phoneNumber, type);
+            log(" processAtClcc phonenumber = "+ phoneNumber + " type = " + type);
+            // call still in dialling or alerting state
+            if (mStateMachineCallState.mNumActive == 0) {
+                mNativeInterface.clccResponse(device, 1, 0, mStateMachineCallState.mCallState, 0,
+                                              false, phoneNumber, type);
+            } else {
+                mNativeInterface.clccResponse(device, 1, 0, 0, 0, false, phoneNumber, type);
+            }
             mNativeInterface.clccResponse(device, 0, 0, 0, 0, false, "", 0);
         } else {
             // In Telecom call, ask Telecom to send send remote phone number
@@ -1821,6 +2499,8 @@
             processAtCpbs(atCommand.substring(5), commandType, device);
         } else if (atCommand.startsWith("+CPBR")) {
             processAtCpbr(atCommand.substring(5), commandType, device);
+        } else if (atCommand.startsWith("+CSQ")) {
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
         } else {
             processVendorSpecificAt(atCommand, device);
         }
@@ -1830,19 +2510,24 @@
     private void processKeyPressed(BluetoothDevice device) {
         if (mSystemInterface.isRinging()) {
             mSystemInterface.answerCall(device);
-        } else if (mSystemInterface.isInCall()) {
-            if (getAudioState() == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-                // Should connect audio as well
-                if (!mHeadsetService.setActiveDevice(mDevice)) {
-                    Log.w(TAG, "processKeyPressed, failed to set active device to " + mDevice);
-                }
-            } else {
-                mSystemInterface.hangupCall(device);
-            }
         } else if (getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
             if (!mNativeInterface.disconnectAudio(mDevice)) {
                 Log.w(TAG, "processKeyPressed, failed to disconnect audio from " + mDevice);
             }
+        } else if (mSystemInterface.isInCall()) {
+            if (getAudioState() == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                // Should connect audio as well
+                if (mDevice.equals(mHeadsetService.getActiveDevice())) {
+                    Log.w(TAG, "processKeyPressed: device "+ mDevice+" is active, create SCO");
+                    mNativeInterface.connectAudio(mDevice);
+                } else {
+                    //Set active device and create SCO
+                    if (!mHeadsetService.setActiveDevice(mDevice)) {
+                        Log.w(TAG, "processKeyPressed, failed to set active device to "
+                              + mDevice);
+                    }
+                }
+            }
         } else {
             // We have already replied OK to this HSP command, no feedback is needed
             if (mHeadsetService.hasDeviceInitiatedDialingOut()) {
@@ -1918,6 +2603,41 @@
         sendIndicatorIntent(device, indId, indValue);
     }
 
+    private void processCpbr(Intent intent)
+    {
+        int atCommandResult = 0;
+        int atCommandErrorCode = 0;
+        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+        log("Enter processCpbr()");
+        // ASSERT: (headset != null) && headSet.isConnected()
+        // REASON: mCheckingAccessPermission is true, otherwise resetAtState
+        // has set mCheckingAccessPermission to false
+        if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
+            if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
+                                   BluetoothDevice.CONNECTION_ACCESS_NO)
+                    == BluetoothDevice.CONNECTION_ACCESS_YES) {
+                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
+                    mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+                }
+                atCommandResult = mPhonebook.processCpbrCommand(device);
+            } else {
+                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
+                    mDevice.setPhonebookAccessPermission(
+                            BluetoothDevice.ACCESS_REJECTED);
+                }
+            }
+        }
+        mPhonebook.setCpbrIndex(-1);
+        mPhonebook.setCheckingAccessPermission(false);
+
+        if (atCommandResult >= 0) {
+            mNativeInterface.atResponseCode(device, atCommandResult, atCommandErrorCode);
+        } else {
+            log("processCpbr - RESULT_NONE");
+        }
+        Log.d(TAG, "Exit processCpbr()");
+    }
+
     private void processSendClccResponse(HeadsetClccResponse clcc) {
         if (!hasMessages(CLCC_RSP_TIMEOUT)) {
             return;
@@ -1925,8 +2645,24 @@
         if (clcc.mIndex == 0) {
             removeMessages(CLCC_RSP_TIMEOUT);
         }
-        mNativeInterface.clccResponse(mDevice, clcc.mIndex, clcc.mDirection, clcc.mStatus,
-                clcc.mMode, clcc.mMpty, clcc.mNumber, clcc.mType);
+        // get the top of the Q
+        HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
+
+        /* Send call state DIALING if call alerting update is still in the Q */
+        if (clcc.mStatus == HeadsetHalConstants.CALL_STATE_ALERTING &&
+            tempCallState != null &&
+            tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING) {
+            log("sending call status as DIALING");
+            mNativeInterface.clccResponse(mDevice, clcc.mIndex, clcc.mDirection,
+                                          HeadsetHalConstants.CALL_STATE_DIALING,
+                                   clcc.mMode, clcc.mMpty, clcc.mNumber, clcc.mType);
+        } else {
+            log("sending call status as " + clcc.mStatus);
+            mNativeInterface.clccResponse(mDevice, clcc.mIndex, clcc.mDirection,
+                                          clcc.mStatus,
+                                   clcc.mMode, clcc.mMpty, clcc.mNumber, clcc.mType);
+        }
+        log("Exit processSendClccResponse()");
     }
 
     private void processSendVendorSpecificResultCode(HeadsetVendorSpecificResultCode resultCode) {
@@ -1963,6 +2699,50 @@
         mSystemInterface.getHeadsetPhoneState().listenForPhoneState(mDevice, events);
     }
 
+    boolean isConnectedDeviceBlacklistedforIncomingCall() {
+        // Checking for the Blacklisted device Addresses
+        for (int j = 0; j < BlacklistDeviceAddrToDelayCallInd.length;j++) {
+            String addr = BlacklistDeviceAddrToDelayCallInd[j];
+            if (mDevice.toString().toLowerCase().startsWith(addr.toLowerCase())) {
+                log("Remote device address Blacklisted for sending delay");
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean isDeviceBlacklistedForSendingCallIndsBackToBack() {
+        // Checking for the Blacklisted device Addresses
+        for (int j = 0; j < BlacklistDeviceForSendingVOIPCallIndsBackToBack.length;j++) {
+            String addr = BlacklistDeviceForSendingVOIPCallIndsBackToBack[j];
+            if (mDevice.toString().toLowerCase().startsWith(addr.toLowerCase())) {
+                Log.w(TAG, "Remote device " + mDevice +
+                   " address Blacklisted for sending VOIP call inds back to back");
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void sendVoipConnectivityNetworktype(boolean isVoipStarted) {
+        Log.d(TAG, "Enter sendVoipConnectivityNetworktype()");
+        NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo();
+        if (networkInfo == null || !networkInfo.isAvailable() || !networkInfo.isConnected()) {
+            Log.d(TAG, "No connected/available connectivity network, don't update soc");
+            return;
+        }
+
+        if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
+            Log.d(TAG, "Voip/VoLTE started/stopped on n/w TYPE_MOBILE, don't update to soc");
+        } else if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+            Log.d(TAG, "Voip/VoLTE started/stopped on n/w TYPE_WIFI, update n/w type & start/stop to soc");
+            mAdapterService.voipNetworkWifiInfo(isVoipStarted, true);
+        } else {
+            Log.d(TAG, "Voip/VoLTE started/stopped on some other n/w, don't update to soc");
+        }
+        Log.d(TAG, "Exit sendVoipConnectivityNetworktype()");
+    }
+
     @Override
     protected void log(String msg) {
         if (DBG) {
@@ -1990,38 +2770,23 @@
     }
 
     private void handleAccessPermissionResult(Intent intent) {
-        log("handleAccessPermissionResult");
+        log("Enter handleAccessPermissionResult");
         BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-        if (!mPhonebook.getCheckingAccessPermission()) {
-            return;
-        }
-        int atCommandResult = 0;
-        int atCommandErrorCode = 0;
-        // HeadsetBase headset = mHandsfree.getHeadset();
-        // ASSERT: (headset != null) && headSet.isConnected()
-        // REASON: mCheckingAccessPermission is true, otherwise resetAtState
-        // has set mCheckingAccessPermission to false
-        if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
-            if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
-                    BluetoothDevice.CONNECTION_ACCESS_NO)
-                    == BluetoothDevice.CONNECTION_ACCESS_YES) {
-                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
-                    mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
-                }
-                atCommandResult = mPhonebook.processCpbrCommand(device);
-            } else {
-                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
-                    mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
-                }
+        if (mPhonebook != null) {
+            if (!mPhonebook.getCheckingAccessPermission()) {
+                return;
+            }
+
+            Message m = obtainMessage(PROCESS_CPBR);
+            m.obj = intent;
+            sendMessage(m);
+        } else {
+            Log.e(TAG, "Phonebook handle null");
+            if (device != null) {
+                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
             }
         }
-        mPhonebook.setCpbrIndex(-1);
-        mPhonebook.setCheckingAccessPermission(false);
-        if (atCommandResult >= 0) {
-            mNativeInterface.atResponseCode(device, atCommandResult, atCommandErrorCode);
-        } else {
-            log("handleAccessPermissionResult - RESULT_NONE");
-        }
+        log("Exit handleAccessPermissionResult()");
     }
 
     private static String getMessageName(int what) {
diff --git a/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
index 81090eb..2593adf 100644
--- a/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
+++ b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
@@ -108,6 +108,11 @@
             // Synchronization should make sure unbind can be successful
             mHeadsetService.unbindService(mPhoneProxyConnection);
         }
+        //sometimes when BT is turned off while in call,
+        // we don't get a chance to move out of AudioOn state
+
+        mAudioManager.setParameters("BT_SCO=off");
+        mAudioManager.setBluetoothScoOn(false);
         mHeadsetPhoneState.cleanup();
     }
 
@@ -329,8 +334,7 @@
     @VisibleForTesting
     public boolean isInCall() {
         return ((mHeadsetPhoneState.getNumActiveCall() > 0) || (mHeadsetPhoneState.getNumHeldCall()
-                > 0) || ((mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE)
-                && (mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_INCOMING)));
+                > 0) || (mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE));
     }
 
     /**
diff --git a/src/com/android/bluetooth/hid/HidHostService.java b/src/com/android/bluetooth/hid/HidHostService.java
index ff1a608..05c7476 100644
--- a/src/com/android/bluetooth/hid/HidHostService.java
+++ b/src/com/android/bluetooth/hid/HidHostService.java
@@ -46,7 +46,7 @@
  * @hide
  */
 public class HidHostService extends ProfileService {
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
     private static final String TAG = "BluetoothHidHostService";
 
     private Map<BluetoothDevice, Integer> mInputDevices;
diff --git a/src/com/android/bluetooth/map/BluetoothMapAccountItem.java b/src/com/android/bluetooth/map/BluetoothMapAccountItem.java
index cb9481b..aa5b969 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAccountItem.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAccountItem.java
@@ -40,10 +40,12 @@
     public final String mBase_uri_no_account;
     private final String mUci;
     private final String mUciPrefix;
+    private String mDisplayName;
+    private String mEmailAddress;
 
     public BluetoothMapAccountItem(String id, String name, String packageName, String authority,
             Drawable icon, BluetoothMapUtils.TYPE appType, String uci, String uciPrefix) {
-        this.mName = name;
+        this.mName = name + " Server " + id;
         this.mIcon = icon;
         this.mPackageName = packageName;
         this.mId = id;
@@ -228,4 +230,23 @@
         return mType;
     }
 
+    public void setDisplayName(String name) {
+        mDisplayName = name;
+        if(V) Log.v(TAG, "setDispName: " + mDisplayName );
+    }
+
+    public void setEmailAddress(String emailId) {
+        mEmailAddress = emailId;
+        if(V) Log.v(TAG, "setOrgEmail: " + mEmailAddress );
+    }
+
+    public String getDisplayName() {
+        if(V) Log.v(TAG, "getDispName: " + mDisplayName );
+        return mDisplayName;
+    }
+
+    public String getEmailAddress() {
+        if(V) Log.v(TAG, "getOrgEmail: " + mDisplayName );
+        return mEmailAddress;
+    }
 }
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppObserver.java b/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
index b4dfe5a..7ed461e 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
@@ -23,10 +23,13 @@
 import android.content.pm.ResolveInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.SystemProperties;
 import android.util.Log;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.AbstractionLayer;
 
 import com.android.bluetooth.mapapi.BluetoothMapContract;
-
+import com.android.bluetooth.R;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -59,9 +62,24 @@
     public BluetoothMapAppObserver(final Context context, BluetoothMapService mapService) {
         mContext = context;
         mMapService = mapService;
-        mResolver = context.getContentResolver();
-        mLoader = new BluetoothMapAccountLoader(mContext);
-        mFullList = mLoader.parsePackages(false); /* Get the current list of apps */
+        mResolver   = context.getContentResolver();
+        AdapterService adapterService = AdapterService.getAdapterService();
+        boolean isEmailSupported = false;
+
+        if (adapterService != null) {
+            isEmailSupported = adapterService.getProfileInfo(AbstractionLayer.MAP, AbstractionLayer.MAP_EMAIL_SUPPORT);
+            Log.d(TAG, "isEmailSupported: " + isEmailSupported);
+        }
+        if (isEmailSupported) {
+            mLoader = new BluetoothMapAccountEmailLoader(mContext);
+        } else {
+            mLoader = new BluetoothMapAccountLoader(mContext);
+        }
+        mFullList   = mLoader.parsePackages(false); /* Get the current list of apps */
+        if (isEmailSupported) {
+            SystemProperties.set("vendor.bluetooth.emailaccountcount",
+                String.valueOf(mLoader.getAccountsEnabledCount()));
+        }
         createReceiver();
         initObservers();
     }
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index c862619..70e797d 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -588,7 +588,7 @@
         }
     }
 
-    private class Event {
+    class Event {
         public String eventType;
         public long handle;
         public String folder = null;
@@ -835,6 +835,8 @@
         public long folderId = -1;     // Email folder ID
         public long oldFolderId = -1;  // Used for email undelete
         public boolean localInitiatedSend = false; // Used for MMS to filter out events
+        boolean localInitiatedReadStatus = false; // Used for SetMsgStatusRead to filter out event
+        boolean localInitiatedShift = false; // Used for SetMsgStatusDelete to filter out events
         public boolean transparent = false;
         // Used for EMAIL to delete message sent with transparency
         public int flagRead = -1;      // Message status read/unread
diff --git a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
index 560558e..6867b56 100644
--- a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
@@ -40,6 +40,7 @@
     private String mName;
     private BluetoothMapFolderElement mParent = null;
     private long mFolderId = -1;
+    private int mFolderType = -1;
     private boolean mHasSmsMmsContent = false;
     private boolean mHasImContent = false;
     private boolean mHasEmailContent = false;
@@ -128,6 +129,17 @@
         return sb.toString();
     }
 
+    public void setFolderType(int folderType) {
+        this.mFolderType = folderType;
+    }
+
+    /**
+     * Return folder typ info
+     * @return a integer for email type.
+     */
+    public int getFolderType() {
+       return mFolderType;
+    }
 
     public BluetoothMapFolderElement getFolderByName(String name) {
         BluetoothMapFolderElement folderElement = this.getRoot();
diff --git a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
index e3df91f..9d1d355 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
@@ -287,9 +287,12 @@
             mAcceptNewConnections = true;
         } else {
 
-            mServerSockets = ObexServerSockets.create(this);
-            mAcceptNewConnections = true;
+            mServerSockets = ObexServerSockets.createWithFixedChannels(this,
+                    (SdpManager.MAP_RFCOMM_CHANNEL +
+                        SdpManager.NEXT_RFCOMM_CHANNEL * mMasInstanceId),
+                    (SdpManager.MAP_L2CAP_PSM + SdpManager.NEXT_L2CAP_CHANNEL * mMasInstanceId));
 
+            mAcceptNewConnections = true;
             if (mServerSockets == null) {
                 // TODO: Handle - was not handled before
                 Log.e(mTag, "Failed to start the listeners");
@@ -361,12 +364,27 @@
 
             mMnsClient = mnsClient;
             BluetoothMapObexServer mapServer;
-            mObserver = new BluetoothMapContentObserver(mContext, mMnsClient, this, mAccount,
-                    mEnableSmsMms);
+            if (mAccount != null && mAccount.getType() == TYPE.EMAIL) {
+                Log.d(mTag, "startObexServerSession getType = " + mAccount.getType());
+                mObserver = new  BluetoothMapContentObserverEmail(mContext,
+                                                         mMnsClient,
+                                                         this,
+                                                         mAccount,
+                                                         mEnableSmsMms);
+            } else {
+                mObserver = new  BluetoothMapContentObserver(mContext,
+                                                         mMnsClient,
+                                                         this,
+                                                         mAccount,
+                                                         mEnableSmsMms);
+            }
             mObserver.init();
-            mapServer =
-                    new BluetoothMapObexServer(mServiceHandler, mContext, mObserver, this, mAccount,
-                            mEnableSmsMms);
+            mapServer = new BluetoothMapObexServer(mServiceHandler,
+                                                    mContext,
+                                                    mObserver,
+                                                    this,
+                                                    mAccount,
+                                                    mEnableSmsMms);
 
             // setup transport
             BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
@@ -398,7 +416,7 @@
         return (mConnSocket != null);
     }
 
-    public void shutdown() {
+    public synchronized void shutdown() {
         if (D) {
             Log.d(mTag, "MAP Service shutdown");
         }
@@ -432,6 +450,7 @@
 
 
     private synchronized void closeServerSockets(boolean block) {
+        if(V) Log.d(mTag, "closeServerSock");
         // exit SocketAcceptThread early
         ObexServerSockets sockets = mServerSockets;
         if (sockets != null) {
@@ -441,6 +460,7 @@
     }
 
     private synchronized void closeConnectionSocket() {
+        if(V) Log.d(mTag, "closeConnectionSock");
         if (mConnSocket != null) {
             try {
                 mConnSocket.close();
@@ -452,11 +472,11 @@
         }
     }
 
-    public void setRemoteFeatureMask(int supportedFeatures) {
-        if (V) {
-            Log.v(mTag, "setRemoteFeatureMask : Curr: " + mRemoteFeatureMask);
-        }
-        mRemoteFeatureMask = supportedFeatures & SDP_MAP_MAS_FEATURES;
+    public void setRemoteFeatureMask(int supportedFeatures, int remoteProfileVersion) {
+       Log.d(mTag, "Current Feature Mask: " + Integer.toHexString(mRemoteFeatureMask)
+               + ", remote feature Mask: " + Integer.toHexString(supportedFeatures));
+       mRemoteFeatureMask = remoteProfileVersion > SDP_MAP_MAS_VERSION ?
+                SDP_MAP_MAS_FEATURES : supportedFeatures;
         if (mObserver != null) {
             mObserver.setObserverRemoteFeatureMask(mRemoteFeatureMask);
             if (V) {
@@ -477,6 +497,7 @@
         /* Signal to the service that we have received an incoming connection.
          */
         boolean isValid = mMapService.onConnect(device, BluetoothMapMasInstance.this);
+        if(V) Log.d(mTag, "onConnect");
 
         if (isValid) {
             mRemoteDevice = device;
diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index afd1eb0..dd8dd48 100644
--- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -32,6 +32,7 @@
 import com.android.bluetooth.SignedLongLong;
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
 import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.android.bluetooth.mapapi.BluetoothMapEmailContract;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -156,12 +157,17 @@
             }
             mProviderClient = acquireUnstableContentProviderOrThrow();
         }
+        if (account != null && account.getType() == TYPE.EMAIL) {
+            mOutContent = new BluetoothMapContentEmail(mContext, mAccount, mMasInstance);
+        } else {
+            mOutContent = new BluetoothMapContent(mContext, mAccount, mMasInstance);
+        }
 
         buildFolderStructure(); /* Build the default folder structure, and set
                                    mCurrentFolder to root folder */
         mObserver.setFolderStructure(mCurrentFolder.getRoot());
 
-        mOutContent = new BluetoothMapContent(mContext, mAccount, mMasInstance);
+
 
     }
 
@@ -214,12 +220,12 @@
         if (mEnableSmsMms) {
             addSmsMmsFolders(tmpFolder);
         }
-        if (hasEmail) {
-            if (D) {
-                Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString());
-            }
-            addEmailFolders(tmpFolder);
+
+        if (hasEmail && (mOutContent instanceof BluetoothMapContentEmail)) {
+            if (D) Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString());
+            ((BluetoothMapContentEmail)mOutContent).addEmailFolders(tmpFolder);
         }
+
         if (hasIM) {
             addImFolders(tmpFolder);
         }
@@ -474,6 +480,11 @@
                 if (V) {
                     Log.d(TAG, "TYPE_MESSAGE_UPDATE:");
                 }
+                if (mAccount!= null && mAccount.getType() == TYPE.EMAIL &&
+                        (mOutContent instanceof BluetoothMapContentEmail)) {
+                    ((BluetoothMapContentEmail)mOutContent).msgUpdate();
+                     return ResponseCodes.OBEX_HTTP_OK;
+                }
                 return updateInbox();
             } else if (type.equals(TYPE_SET_NOTIFICATION_REGISTRATION)) {
                 if (V) {
@@ -660,7 +671,8 @@
                 folderName = folderElement.getName();
             }
             if (!folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX) && !folderName
-                    .equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
+                    .equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)  &&
+                    !folderName.equalsIgnoreCase(BluetoothMapEmailContract.FOLDER_NAME_DRAFTS)) {
                 if (D) {
                     Log.d(TAG, "pushMessage: Is only allowed to outbox and draft. " + "folderName="
                             + folderName);
@@ -783,6 +795,7 @@
 
         long handle;
         BluetoothMapUtils.TYPE msgType;
+        if (D) Log.d(TAG, "setMessageStatus():");
 
         if (msgHandle == null) {
             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
@@ -948,10 +961,16 @@
                 mCurrentFolder = mCurrentFolder.getRoot();
             }
         } else {
+            if (mAccount!= null && mAccount.getType() == TYPE.EMAIL) {
+                if (folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
+                    folderName = BluetoothMapEmailContract.FOLDER_NAME_DRAFTS;
+                }
+            }
             folder = mCurrentFolder.getSubFolder(folderName);
             if (folder != null) {
                 mCurrentFolder = folder;
             } else {
+                Log.w(TAG,"SubFolder Not found! : " + mCurrentFolder.getName());
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
         }
@@ -1519,8 +1538,20 @@
             if (maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
                 maxListCount = 1024;
             }
-
-            if (maxListCount != 0) {
+            try {
+                if ( (mAccount != null && mAccount.getType() == TYPE.EMAIL) &&
+                    (mOutContent instanceof BluetoothMapContentEmail) &&
+                        !mCurrentFolder.getName().equals("telecom") &&
+                            !mCurrentFolder.getName().equals("root")) {
+                    if (D) Log.d(TAG, "RefreshFolderStructure(): " +  mCurrentFolder.getName());
+                    ((BluetoothMapContentEmail)mOutContent).addEmailFolders(mCurrentFolder);
+                }
+            } catch( RemoteException e1) {
+                Log.v(TAG,"sendFolderList Refresh failed : Go with existing for :"
+                    + mCurrentFolder.getName());
+            }
+            if(maxListCount != 0)
+            {
                 outBytes = mCurrentFolder.encode(listStartOffset, maxListCount);
             } else {
                 // ESR08 specified that this shall only be included for MaxListCount=0
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index bfb1d3f..4248435 100644
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -29,6 +29,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentFilter.MalformedMimeTypeException;
+import android.Manifest;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -36,6 +37,7 @@
 import android.os.ParcelUuid;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
@@ -56,7 +58,7 @@
 
 public class BluetoothMapService extends ProfileService {
     private static final String TAG = "BluetoothMapService";
-
+    private static final String LOG_TAG = "BluetoothMap";
     /**
      * To enable MAP DEBUG/VERBOSE logging - run below cmd in adb shell, and
      * restart com.android.bluetooth process. only enable DEBUG log:
@@ -66,7 +68,7 @@
 
     public static final boolean DEBUG = true; //FIXME set to false;
 
-    public static final boolean VERBOSE = false;
+    public static final boolean VERBOSE = Log.isLoggable(LOG_TAG, Log.VERBOSE);
 
     /**
      * Intent indicating timeout for user confirmation, which is sent to
@@ -97,8 +99,8 @@
     private static final int USER_TIMEOUT = 2;
     private static final int DISCONNECT_MAP = 3;
     private static final int SHUTDOWN = 4;
-    private static final int UPDATE_MAS_INSTANCES = 5;
-
+    private static final int UPDATE_MAS_INSTANCES = 6;
+    private static final int CREATE_MAS_INSTANCES = 5;
     private static final int RELEASE_WAKE_LOCK_DELAY = 10000;
     private PowerManager.WakeLock mWakeLock = null;
 
@@ -123,11 +125,11 @@
     // The remote connected device - protect access
     private static BluetoothDevice sRemoteDevice = null;
 
-    private ArrayList<BluetoothMapAccountItem> mEnabledAccounts = null;
+    ArrayList<BluetoothMapAccountItem> mEnabledAccounts = null;
     private static String sRemoteDeviceName = null;
 
     private int mState;
-    private BluetoothMapAppObserver mAppObserver = null;
+    BluetoothMapAppObserver mAppObserver = null;
     private AlarmManager mAlarmManager = null;
 
     private boolean mIsWaitingAuthorization = false;
@@ -195,7 +197,7 @@
         }
         mSessionStatusHandler = null;
 
-        if (VERBOSE) {
+        if (DEBUG) {
             Log.v(TAG, "MAP Service closeService out");
         }
     }
@@ -268,7 +270,7 @@
                 mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK),
                 RELEASE_WAKE_LOCK_DELAY);
 
-        if (VERBOSE) {
+        if (DEBUG) {
             Log.v(TAG, "startObexServerSessions() success!");
         }
     }
@@ -283,7 +285,7 @@
      */
     private void stopObexServerSessions(int masId) {
         if (DEBUG) {
-            Log.d(TAG, "MAP Service STOP ObexServerSessions()");
+            Log.d(TAG, "MAP Service STOP ObexServerSessions() masId: " + masId);
         }
 
         boolean lastMasInst = true;
@@ -298,9 +300,13 @@
         } // Else just close down it all
 
         // Shutdown the MNS client - this must happen before MAS close
-        if (mBluetoothMnsObexClient != null && lastMasInst) {
-            mBluetoothMnsObexClient.shutdown();
-            mBluetoothMnsObexClient = null;
+        if (mBluetoothMnsObexClient != null ) {
+            if (lastMasInst) {
+                mBluetoothMnsObexClient.shutdown();
+                mBluetoothMnsObexClient = null;
+            } else if(masId != -1 && mMasInstances != null) {
+                BluetoothMapFixes.handleMnsShutdown(mMasInstances,masId);
+            }
         }
 
         BluetoothMapMasInstance masInst = mMasInstances.get(masId); // returns null for -1
@@ -333,17 +339,23 @@
     }
 
     private final class MapServiceMessageHandler extends Handler {
-        private MapServiceMessageHandler(Looper looper) {
+        Context mContxt;
+        private MapServiceMessageHandler(Context contxt, Looper looper) {
             super(looper);
+            mContxt = contxt;
         }
 
         @Override
         public void handleMessage(Message msg) {
-            if (VERBOSE) {
+            if (DEBUG) {
                 Log.v(TAG, "Handler(): got msg=" + msg.what);
             }
 
             switch (msg.what) {
+                case CREATE_MAS_INSTANCES:
+                    Log.d(TAG, "CREATE_MAS_INSTANCES ");
+                    BluetoothMapFixes.createMasInstances(BluetoothMapService.this);
+                    break;
                 case UPDATE_MAS_INSTANCES:
                     updateMasInstancesHandler();
                     break;
@@ -600,7 +612,7 @@
         HandlerThread thread = new HandlerThread("BluetoothMapHandler");
         thread.start();
         Looper looper = thread.getLooper();
-        mSessionStatusHandler = new MapServiceMessageHandler(looper);
+        mSessionStatusHandler = new MapServiceMessageHandler(this, looper);
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
@@ -618,17 +630,26 @@
             Log.e(TAG, "Wrong mime type!!!", e);
         }
         if (!mRegisteredMapReceiver) {
-            registerReceiver(mMapReceiver, filter);
-            registerReceiver(mMapReceiver, filterMessageSent);
-            mRegisteredMapReceiver = true;
+            try {
+                registerReceiver(mMapReceiver, filter);
+                // We need WRITE_SMS permission to handle messages in
+                // actionMessageSentDisconnected()
+                registerReceiver(mMapReceiver, filterMessageSent,
+                        Manifest.permission.WRITE_SMS, null);
+                mRegisteredMapReceiver = true;
+            } catch (Exception e) {
+                Log.e(TAG,"Unable to register map receiver",e);
+            }
         }
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        mAppObserver = new BluetoothMapAppObserver(this, this);
+
+        // Move SDP records creation to Handler Thread instead of main thread.
+        BluetoothMapFixes.sendCreateMasInstances(this, CREATE_MAS_INSTANCES);
 
         mSmsCapable = getResources().getBoolean(com.android.internal.R.bool.config_sms_capable);
-
-        mEnabledAccounts = mAppObserver.getEnabledAccountItems();
-        createMasInstances();  // Uses mEnabledAccounts
+        if (DEBUG) {
+             Log.d(TAG, "mSmsCapable :" + mSmsCapable);
+        }
 
         sendStartListenerMessage(-1);
         setBluetoothMapService(this);
@@ -690,8 +711,9 @@
         if (DEBUG) {
             Log.d(TAG, "updateMasInstancesHandler() state = " + getState());
         }
-
-        if (getState() != BluetoothMap.STATE_DISCONNECTED) {
+        if (BluetoothMapFixes.checkMapAppObserver(mAppObserver))
+            return;
+        if (getState() == BluetoothMap.STATE_CONNECTING) {
             mAccountChanged = true;
             return;
         }
@@ -699,6 +721,10 @@
         ArrayList<BluetoothMapAccountItem> newAccountList = mAppObserver.getEnabledAccountItems();
         ArrayList<BluetoothMapAccountItem> newAccounts = new ArrayList<>();
 
+        Log.d(TAG, "new Account List size = " + newAccountList.size());
+        SystemProperties.set("vendor.bluetooth.emailaccountcount",
+                String.valueOf(newAccountList.size()));
+
         for (BluetoothMapAccountItem account : newAccountList) {
             if (!mEnabledAccounts.remove(account)) {
                 newAccounts.add(account);
@@ -776,7 +802,7 @@
         return 0xff; // This will never happen, as we only allow 10 e-mail accounts to be enabled
     }
 
-    private void createMasInstances() {
+    void createMasInstances() {
         int masId = MAS_ID_SMS_MMS;
 
         if (mSmsCapable) {
@@ -788,13 +814,15 @@
             masId++;
         }
 
-        // get list of accounts already set to be visible through MAP
-        for (BluetoothMapAccountItem account : mEnabledAccounts) {
-            BluetoothMapMasInstance newInst =
-                    new BluetoothMapMasInstance(this, this, account, masId, false);
-            mMasInstances.append(masId, newInst);
-            mMasInstanceMap.put(account, newInst);
-            masId++;
+        if (mEnabledAccounts != null) {
+            // get list of accounts already set to be visible through MAP
+            for (BluetoothMapAccountItem account : mEnabledAccounts) {
+                BluetoothMapMasInstance newInst =
+                        new BluetoothMapMasInstance(this, this, account, masId, false);
+                mMasInstances.append(masId, newInst);
+                mMasInstanceMap.put(account, newInst);
+                masId++;
+            }
         }
     }
 
@@ -814,8 +842,10 @@
         if (mRegisteredMapReceiver) {
             mRegisteredMapReceiver = false;
             unregisterReceiver(mMapReceiver);
-            mAppObserver.shutdown();
+            if (mAppObserver != null)
+                mAppObserver.shutdown();
         }
+        setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
         sendShutdownMessage();
         return true;
     }
@@ -851,7 +881,7 @@
                     sRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
                     mSdpSearchInitiated = true;
                 }
-            } else if (!sRemoteDevice.equals(remoteDevice)) {
+            } else if (!remoteDevice.equals(sRemoteDevice)) {
                 Log.w(TAG, "Unexpected connection from a second Remote Device received. name: " + (
                         (remoteDevice == null) ? "unknown" : remoteDevice.getName()));
                 return false;
@@ -955,6 +985,7 @@
     }
 
     private void sendShutdownMessage() {
+        if (VERBOSE) Log.d(TAG, "sendShutdownMessage() In");
         // Pending messages are no longer valid. To speed up things, simply delete them.
         if (mRemoveTimeoutMsg) {
             Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
@@ -1068,7 +1099,8 @@
                     if (status != -1 && mMnsRecord != null) {
                         for (int i = 0, c = mMasInstances.size(); i < c; i++) {
                             mMasInstances.valueAt(i)
-                                    .setRemoteFeatureMask(mMnsRecord.getSupportedFeatures());
+                                    .setRemoteFeatureMask(mMnsRecord.getSupportedFeatures(),
+                                    mMnsRecord.getProfileVersion());
                         }
                     }
                     if (mSdpSearchInitiated) {
@@ -1097,23 +1129,27 @@
                 }
                 if (!handled) {
                     // Move the SMS to the correct folder.
-                    BluetoothMapContentObserver.actionMessageSentDisconnected(context, intent,
-                            result);
+                    try {
+                        BluetoothMapContentObserver.actionMessageSentDisconnected(context, intent,
+                                result);
+                    } catch(IllegalArgumentException e) {
+                        return;
+                    }
                 }
-            } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)
-                    && mIsWaitingAuthorization) {
+            } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
 
                 if (sRemoteDevice == null || device == null) {
-                    Log.e(TAG, "Unexpected error!");
+                    Log.i(TAG, "sRemoteDevice :" + sRemoteDevice + " device:" + device);
                     return;
                 }
 
                 if (VERBOSE) {
-                    Log.v(TAG, "ACL disconnected for " + device);
+                    Log.v(TAG, "ACL disconnected for " + device
+                            + " mIsWaitingAuthorization :" + mIsWaitingAuthorization);
                 }
 
-                if (sRemoteDevice.equals(device)) {
+                if (mIsWaitingAuthorization && device.equals(sRemoteDevice)) {
                     // Send any pending timeout now, since ACL got disconnected
                     mSessionStatusHandler.removeMessages(USER_TIMEOUT);
                     mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
@@ -1282,7 +1318,8 @@
         println(sb, "mRemoteDevice: " + sRemoteDevice);
         println(sb, "sRemoteDeviceName: " + sRemoteDeviceName);
         println(sb, "mState: " + mState);
-        println(sb, "mAppObserver: " + mAppObserver);
+        if (mAppObserver != null)
+            println(sb, "mAppObserver: " + mAppObserver);
         println(sb, "mIsWaitingAuthorization: " + mIsWaitingAuthorization);
         println(sb, "mRemoveTimeoutMsg: " + mRemoveTimeoutMsg);
         println(sb, "mPermission: " + mPermission);
@@ -1293,8 +1330,10 @@
             println(sb, "  " + key + " : " + mMasInstanceMap.get(key));
         }
         println(sb, "mEnabledAccounts:");
-        for (BluetoothMapAccountItem account : mEnabledAccounts) {
-            println(sb, "  " + account);
+        if (mEnabledAccounts != null) {
+            for (BluetoothMapAccountItem account : mEnabledAccounts) {
+                println(sb, "  " + account);
+            }
         }
     }
 }
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessage.java b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
index e222aa9..55e5933 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessage.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
@@ -30,6 +30,7 @@
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 
+import com.android.bluetooth.map.BluetoothMapCommonUtils.BMsgReaderExt;
 public abstract class BluetoothMapbMessage {
 
     protected static final String TAG = "BluetoothMapbMessage";
@@ -58,7 +59,9 @@
 
     private ArrayList<VCard> mOriginator = null;
     private ArrayList<VCard> mRecipient = null;
-
+    private ArrayList<String> mRecipientTo = null;
+    private ArrayList<String> mRecipientCc = null;
+    private ArrayList<String> mRecipientBCc = null;
 
     public static class VCard {
         /* VCARD attributes */
@@ -194,6 +197,13 @@
             }
         }
 
+        public String[] getEmailAddresses() {
+            if (mEmailAddresses.length > 0) {
+                return mEmailAddresses;
+            } else
+                throw new IllegalArgumentException("No Recipient Email Address");
+        }
+
         public String getFirstBtUci() {
             if (mBtUcis.length > 0) {
                 return mBtUcis[0];
@@ -279,6 +289,7 @@
                     }
                     // Empty phone number - ignore
                 } else if (line.startsWith("EMAIL:")) {
+                    line = line.replace("&lt;", "<").replace("&gt;", ">");
                     parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
                     if (parts.length == 2) {
                         String[] subParts = parts[1].split("[^\\\\];");
@@ -323,7 +334,7 @@
 
     ;
 
-    private static class BMsgReader {
+    protected static class BMsgReader {
         InputStream mInStream;
 
         BMsgReader(InputStream is) {
@@ -371,15 +382,14 @@
         /**
          * Read a line of text from the BMessage.
          * @return the next line of text, or null at end of file, or if UTF-8 is not supported.
+         * @hide
          */
         public String getLine() {
             try {
                 byte[] line = getLineAsBytes();
-                if (line.length == 0) {
+                if (BluetoothMapFixes.isLastLine(line))
                     return null;
-                } else {
-                    return new String(line, "UTF-8");
-                }
+                return new String(line, "UTF-8");
             } catch (UnsupportedEncodingException e) {
                 Log.w(TAG, e);
                 return null;
@@ -485,7 +495,7 @@
 
     public static BluetoothMapbMessage parse(InputStream bMsgStream, int appParamCharset)
             throws IllegalArgumentException {
-        BMsgReader reader;
+        BMsgReaderExt reader;
         String line = "";
         BluetoothMapbMessage newBMsg = null;
         boolean status = false;
@@ -561,7 +571,7 @@
             Log.i(TAG, "The incoming bMessage have been dumped to " + file.getAbsolutePath());
         } /* End of if(V) log-section */
 
-        reader = new BMsgReader(bMsgStream);
+        reader = new BluetoothMapCommonUtils.BMsgReaderExt(bMsgStream);
         reader.expect("BEGIN:BMSG");
         reader.expect("VERSION");
 
@@ -610,7 +620,7 @@
                             newBMsg = new BluetoothMapbMessageMime();
                             break;
                         case EMAIL:
-                            newBMsg = new BluetoothMapbMessageEmail();
+                        newBMsg = new BluetoothMapbMessageExtEmail();
                             break;
                         case IM:
                             newBMsg = new BluetoothMapbMessageMime();
@@ -654,6 +664,10 @@
         }
         if (line.contains("BEGIN:BENV")) {
             newBMsg.parseEnvelope(reader, 0);
+            if ( type == TYPE.EMAIL && newBMsg instanceof BluetoothMapbMessageExtEmail) {
+                ((BluetoothMapbMessageExtEmail)newBMsg)
+                    .parseBodyEmail(reader.getLastStringTerminator("END:BBODY"));
+            }
         } else {
             throw new IllegalArgumentException("Bmessage has no BEGIN:BENV - line:" + line);
         }
@@ -697,7 +711,7 @@
             }
             parseEnvelope(reader, ++level); // Nested BENV
         }
-        if (line.contains("BEGIN:BBODY")) {
+        if (mType != TYPE.EMAIL && line.contains("BEGIN:BBODY")) {
             if (D) {
                 Log.d(TAG, "Decoding bbody");
             }
@@ -1064,4 +1078,26 @@
             return null;
         }
     }
+    public ArrayList<String> getmRecipientTo() {
+        return mRecipientTo;
+    }
+
+    public void setmRecipientTo(ArrayList<String> mRecipientTo) {
+        this.mRecipientTo = mRecipientTo;
+    }
+    public ArrayList<String> getmRecipientCc() {
+        return mRecipientCc;
+    }
+
+    public void setmRecipientCc(ArrayList<String> mRecipientCc) {
+        this.mRecipientCc = mRecipientCc;
+    }
+
+    public ArrayList<String> getmRecipientBCc() {
+        return mRecipientBCc;
+    }
+
+    public void setmRecipientBCc(ArrayList<String> mRecipientBCc) {
+        this.mRecipientBCc = mRecipientBCc;
+    }
 }
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
index a247bff..1101ba0 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
@@ -153,7 +153,7 @@
     private ArrayList<Rfc822Token> mBcc = null;    // Can be empty
     private ArrayList<Rfc822Token> mReplyTo = null; // Can be empty
     private String mMessageId = null;
-    private ArrayList<MimePart> mParts = null;
+    ArrayList<MimePart> mParts = null;
     private String mContentType = null;
     private String mBoundary = null;
     private boolean mTextonly = false;
@@ -161,7 +161,7 @@
     private boolean mHasHeaders = false;
     private String mMyEncoding = null;
 
-    private String getBoundary() {
+    String getBoundary() {
         // Include "=_" as these cannot occur in quoted printable text
         if (mBoundary == null) {
             mBoundary = "--=_" + UUID.randomUUID();
diff --git a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
index eba6485..2f2af1d 100644
--- a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
+++ b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
@@ -222,7 +222,6 @@
             if (looper != null) {
                 looper.quit();
             }
-            mHandler = null;
         }
 
         /* Disconnect if connected */
diff --git a/src/com/android/bluetooth/mapclient/MnsService.java b/src/com/android/bluetooth/mapclient/MnsService.java
index c1ab39e..95f937f 100644
--- a/src/com/android/bluetooth/mapclient/MnsService.java
+++ b/src/com/android/bluetooth/mapclient/MnsService.java
@@ -59,7 +59,8 @@
         }
         sContext = context;
         sAcceptThread = new SocketAcceptor();
-        sServerSockets = ObexServerSockets.create(sAcceptThread);
+        sServerSockets = ObexServerSockets.createWithFixedChannels(sAcceptThread,
+                SdpManager.MNS_RFCOMM_CHANNEL, SdpManager.MNS_L2CAP_PSM);
         SdpManager sdpManager = SdpManager.getDefaultManager();
         if (sdpManager == null) {
             Log.e(TAG, "SdpManager is null");
diff --git a/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java b/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
index c7029ce..7a1c5da 100644
--- a/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
+++ b/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
@@ -31,6 +31,7 @@
 
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
@@ -50,6 +51,7 @@
     private static final int AVRCP_MAX_VOL = 127;
     private static int sDeviceMaxVolume = 0;
 
+    private AdapterService mAdapterService;
     private MediaPlayerList mMediaPlayerList;
     private AudioManager mAudioManager;
     private AvrcpBroadcastReceiver mReceiver;
@@ -139,12 +141,19 @@
 
         Log.i(TAG, "Starting the AVRCP Target Service");
         mCurrentData = new MediaData(null, null, null);
+        mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
+                "AdapterService cannot be null when A2dpService starts");
 
         mReceiver = new AvrcpBroadcastReceiver();
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
         registerReceiver(mReceiver, filter);
 
+        if(mAdapterService.isVendorIntfEnabled()) {
+            Log.i(TAG, "Vendor Stack is enabled, using legacy implementation");
+            SystemProperties.set(AVRCP_ENABLE_PROPERTY, "false");
+        }
+
         if (!SystemProperties.getBoolean(AVRCP_ENABLE_PROPERTY, true)) {
             Log.w(TAG, "Skipping initialization of the new AVRCP Target Service");
             sInstance = null;
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBatch.java b/src/com/android/bluetooth/opp/BluetoothOppBatch.java
index a66a667..824ded6 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBatch.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBatch.java
@@ -150,6 +150,7 @@
             BluetoothOppShareInfo info = mShares.get(i);
 
             if (info.mStatus < 200) {
+                BTOppUtils.updateFileNameInDb(mContext, info);
                 if (info.mDirection == BluetoothShare.DIRECTION_INBOUND && info.mFilename != null) {
                     new File(info.mFilename).delete();
                 }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java b/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
index 0253e24..42da777 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
@@ -106,7 +106,9 @@
         super.onPause();
 
         if (!mOppManager.mSendingFlag) {
+            BluetoothOppManager.isReadyForFileSharing = false;
             mOppManager.cleanUpSendingFileInfo();
+            BluetoothOppManager.isReadyForFileSharing = true;
         }
     }
 }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
index 6e83480..9a81ce8 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
@@ -32,7 +32,8 @@
     @Override
     public void onReceive(Context context, Intent intent) {
         String action = intent.getAction();
-
+        if(D) Log.d(TAG, " Action :" + action);
+        if (action == null) return;
         if (action.equals(Constants.ACTION_HANDOVER_SEND) || action.equals(
                 Constants.ACTION_HANDOVER_SEND_MULTIPLE)) {
             final BluetoothDevice device =
diff --git a/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java b/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
index 1b5ffe1..8eda73c 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
@@ -151,7 +151,7 @@
                     mUpdateValues.put(BluetoothShare.USER_CONFIRMATION,
                             BluetoothShare.USER_CONFIRMATION_CONFIRMED);
                     this.getContentResolver().update(mUri, mUpdateValues, null, null);
-
+                    if (D) Log.v(TAG, " Confirmed :" + mUri);
                     Toast.makeText(this, getString(R.string.bt_toast_1), Toast.LENGTH_SHORT).show();
                 }
                 break;
@@ -162,6 +162,7 @@
                 mUpdateValues.put(BluetoothShare.USER_CONFIRMATION,
                         BluetoothShare.USER_CONFIRMATION_DENIED);
                 this.getContentResolver().update(mUri, mUpdateValues, null, null);
+                if (D) Log.v(TAG, " Denied :" + mUri);
                 break;
         }
     }
@@ -222,7 +223,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case DISMISS_TIMEOUT_DIALOG:
-                    if (V) {
+                    if (D) {
                         Log.v(TAG, "Received DISMISS_TIMEOUT_DIALOG msg.");
                     }
                     finish();
diff --git a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
index 99e3e69..d952478 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
@@ -61,7 +61,7 @@
  * selection dialog.
  */
 public class BluetoothOppLauncherActivity extends Activity {
-    private static final String TAG = "BluetoothLauncherActivity";
+    private static final String TAG = "BluetoothOppLauncherActivity";
     private static final boolean D = Constants.DEBUG;
     private static final boolean V = Constants.VERBOSE;
 
@@ -75,6 +75,11 @@
 
         Intent intent = getIntent();
         String action = intent.getAction();
+        if (action == null) {
+            Log.w(TAG, " Received " + intent + " with null action");
+            finish();
+            return;
+        }
 
         if (action.equals(Intent.ACTION_SEND) || action.equals(Intent.ACTION_SEND_MULTIPLE)) {
             //Check if Bluetooth is available in the beginning instead of at the end
@@ -88,6 +93,17 @@
                 return;
             }
 
+            if (!BluetoothOppManager.isReadyForFileSharing) {
+                Log.i(TAG, " File share already in process, retrun with out any action ");
+                finish();
+                return;
+            }
+            /*
+             * SECURITY_EXCEPTION Google Photo grant-uri-permission
+             */
+            BTOppUtils.grantPermissionToUri(getApplicationContext(),
+                    intent.getClipData());
+
             /*
              * Other application is trying to share a file via Bluetooth,
              * probably Pictures, videos, or vCards. The Intent should contain
@@ -159,6 +175,7 @@
                         @Override
                         public void run() {
                             try {
+                                BluetoothOppManager.isReadyForFileSharing = false;
                                 BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
                                         .saveSendingFileInfo(mimeType, uris, false /* isHandover */,
                                                 true /* fromExternal */);
@@ -167,7 +184,8 @@
                                 launchDevicePicker();
                                 finish();
                             } catch (IllegalArgumentException exception) {
-                                showToast(exception.getMessage());
+                                Log.e(TAG, "SEND_MULTIPLE :" +exception.getMessage());
+                                BluetoothOppManager.isReadyForFileSharing = true;
                                 finish();
                             }
                         }
@@ -230,6 +248,7 @@
             }
             startActivity(in1);
         }
+        BluetoothOppManager.isReadyForFileSharing = true;
     }
 
     /* Returns true if Bluetooth is allowed given current airplane mode settings. */
@@ -403,12 +422,14 @@
             boolean fromExternal) {
         BluetoothOppManager manager = BluetoothOppManager.getInstance(getApplicationContext());
         try {
+            BluetoothOppManager.isReadyForFileSharing = false;
             manager.saveSendingFileInfo(mimeType, uriString, isHandover, fromExternal);
             launchDevicePicker();
             finish();
         } catch (IllegalArgumentException exception) {
-            showToast(exception.getMessage());
+            Log.e(TAG, "sendFileInfo :" + exception.getMessage());
             finish();
+            BluetoothOppManager.isReadyForFileSharing = true;
         }
     }
 
diff --git a/src/com/android/bluetooth/opp/BluetoothOppManager.java b/src/com/android/bluetooth/opp/BluetoothOppManager.java
index 129d110..7d2d318 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppManager.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppManager.java
@@ -100,6 +100,8 @@
 
     private static final int ALLOWED_INSERT_SHARE_THREAD_NUMBER = 3;
 
+    static boolean isReadyForFileSharing = true;
+
     // used to judge if need continue sending process after received a
     // ENABLED_ACTION
     public boolean mSendingFlag;
@@ -194,6 +196,23 @@
         return false;
     }
 
+    synchronized void removeWhitelist(String address) {
+        Log.d(TAG, " removeWhitelist :" + address);
+        if (address == null) {
+            return;
+        }
+        // Remove any existing entries
+        for (Iterator<Pair<String, Long>> iter = mWhitelist.iterator(); iter.hasNext(); ) {
+            Pair<String, Long> entry = iter.next();
+            if (entry.first.equals(address)) {
+                iter.remove();
+                Log.i(TAG," removeWhitelist device found removed ");
+            }
+        }
+        Log.d(TAG," removeWhitelist END :");
+    }
+
+
     /**
      * Restore data from preference
      */
diff --git a/src/com/android/bluetooth/opp/BluetoothOppNotification.java b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
index 41ffec3..ee7cadd 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppNotification.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
@@ -165,7 +165,7 @@
     public void updateNotification() {
         synchronized (BluetoothOppNotification.this) {
             mPendingUpdate++;
-            if (mPendingUpdate > 1) {
+            if ((mPendingUpdate > 1) && (mUpdateNotificationThread != null)) {
                 if (V) {
                     Log.v(TAG, "update too frequent, put in queue");
                 }
@@ -594,6 +594,7 @@
                             .setLocalOnly(true)
                             .build();
             mNotificationMgr.notify(NOTIFICATION_ID_PROGRESS, n);
+            Log.i(TAG, " Incoming Notification ");
         }
         cursor.close();
     }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
index 1d01646..349951c 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
@@ -490,7 +490,7 @@
 
                         if (responseCode == ResponseCodes.OBEX_HTTP_CONTINUE
                                 || responseCode == ResponseCodes.OBEX_HTTP_OK) {
-                            if (V) {
+                            if (D) {
                                 Log.v(TAG, "Remote accept");
                             }
                             okToProceed = true;
@@ -503,7 +503,7 @@
                             Log.i(TAG, "Remote reject, Response code is " + responseCode);
                         }
                     }
-
+                    long beginTime = System.currentTimeMillis();
                     while (!mInterrupted && okToProceed && (position < fileInfo.mLength)) {
                         if (V) {
                             timestamp = SystemClock.elapsedRealtime();
@@ -556,6 +556,7 @@
                         Log.i(TAG,
                                 "SendFile finished send out file " + fileInfo.mFileName + " length "
                                         + fileInfo.mLength);
+                        BTOppUtils.throughputInKbps(fileInfo.mLength, beginTime);
                     } else {
                         error = true;
                         status = BluetoothShare.STATUS_CANCELED;
@@ -586,7 +587,7 @@
                     if (!error) {
                         responseCode = putOperation.getResponseCode();
                         if (responseCode != -1) {
-                            if (V) {
+                            if (D) {
                                 Log.v(TAG, "Get response code " + responseCode);
                             }
                             if (responseCode != ResponseCodes.OBEX_HTTP_OK) {
@@ -643,7 +644,7 @@
             super.interrupt();
             synchronized (this) {
                 if (mWaitingForRemote) {
-                    if (V) {
+                    if (D) {
                         Log.v(TAG, "Interrupted when waitingForRemote");
                     }
                     try {
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
index 47e80c8..dc07478 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
@@ -104,6 +104,10 @@
 
     private int mNumFilesAttemptedToReceive;
 
+    private boolean isHandover = false;
+
+    private String destination;
+
     public BluetoothOppObexServerSession(Context context, ObexTransport transport,
             BluetoothOppService service) {
         mContext = context;
@@ -112,6 +116,7 @@
         PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         mPartialWakeLock.setReferenceCounted(false);
+        BTOppUtils.acquireFullWakeLock(pm, TAG);
     }
 
     @Override
@@ -129,6 +134,9 @@
                 Log.d(TAG, "Create ServerSession with transport " + mTransport.toString());
             }
             mSession = new ServerSession(mTransport, this, null);
+            if(BTOppUtils.isA2DPPlaying) {
+                mSession.reduceMTU(true);
+            }
         } catch (IOException e) {
             Log.e(TAG, "Create server session error" + e);
         }
@@ -198,15 +206,13 @@
         } else {
             destination = "FF:FF:FF:00:00:00";
         }
-        boolean isWhitelisted =
-                BluetoothOppManager.getInstance(mContext).isWhitelisted(destination);
 
         HeaderSet request;
         String name, mimeType;
         Long length;
         try {
             request = op.getReceivedHeader();
-            if (V) {
+            if (D) {
                 Constants.logHeader(request);
             }
             name = (String) request.getHeader(HeaderSet.NAME);
@@ -260,7 +266,7 @@
         }
 
         // Reject anything outside the "whitelist" plus unspecified MIME Types.
-        if (mimeType == null || (!isWhitelisted && !Constants.mimeTypeMatches(mimeType,
+        if (mimeType == null || (!isHandover && !Constants.mimeTypeMatches(mimeType,
                 Constants.ACCEPTABLE_SHARE_INBOUND_TYPES))) {
             if (D) {
                 Log.w(TAG, "mimeType is null or in unacceptable list, reject the transfer");
@@ -275,29 +281,31 @@
         values.put(BluetoothShare.DESTINATION, destination);
         values.put(BluetoothShare.DIRECTION, BluetoothShare.DIRECTION_INBOUND);
         values.put(BluetoothShare.TIMESTAMP, mTimestamp);
-
+        boolean needConfirm = true;
         // It's not first put if !serverBlocking, so we auto accept it
         if (!mServerBlocking && (mAccepted == BluetoothShare.USER_CONFIRMATION_CONFIRMED
                 || mAccepted == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED)) {
             values.put(BluetoothShare.USER_CONFIRMATION,
                     BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED);
+            needConfirm = false;
         }
 
-        if (isWhitelisted) {
+        if (isHandover) {
             values.put(BluetoothShare.USER_CONFIRMATION,
                     BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
+            needConfirm = false;
         }
 
         Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
         mLocalShareInfoId = Integer.parseInt(contentUri.getPathSegments().get(1));
-
-        if (V) {
+        BTOppUtils.isTurnOnScreen(mContext ,needConfirm);
+        if (D) {
             Log.v(TAG, "insert contentUri: " + contentUri);
             Log.v(TAG, "mLocalShareInfoId = " + mLocalShareInfoId);
         }
 
         synchronized (this) {
-            mPartialWakeLock.acquire();
+            BTOppUtils.acquirePartialWakeLock(mPartialWakeLock);
             mServerBlocking = true;
             try {
 
@@ -339,7 +347,7 @@
         }
         mAccepted = mInfo.mConfirm;
 
-        if (V) {
+        if (D) {
             Log.v(TAG, "after confirm: userAccepted=" + mAccepted);
         }
         int status = BluetoothShare.STATUS_SUCCESS;
@@ -390,6 +398,8 @@
                     mInfo.mStatus = status;
                     msg.obj = mInfo;
                     msg.sendToTarget();
+                } else {
+                    Log.w(TAG," Not sent MSG_SESSION_ERROR ");
                 }
             }
         } else if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED
@@ -429,6 +439,7 @@
         /*
          * implement receive file
          */
+        long beginTime = 0;
         int status = -1;
         BufferedOutputStream bos = null;
 
@@ -466,6 +477,7 @@
             long currentTime;
             long prevTimestamp = SystemClock.elapsedRealtime();
             try {
+                beginTime = System.currentTimeMillis();
                 while ((!mInterrupted) && (position != fileInfo.mLength)) {
 
                     if (V) {
@@ -508,9 +520,15 @@
                 /* OBEX Abort packet received from remote device */
                 if ("Abort Received".equals(e1.getMessage())) {
                     status = BluetoothShare.STATUS_CANCELED;
+                    Message msg = Message.obtain(mCallback,
+                            BluetoothOppObexSession.MSG_SESSION_ERROR);
+                    msg.obj = mInfo;
+                    msg.sendToTarget();
+                    mCallback = null;
                 } else {
                     status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
                 }
+                BTOppUtils.cleanFile(mFileInfo.mFileName);
                 error = true;
             }
         }
@@ -525,6 +543,7 @@
                 if (D) {
                     Log.d(TAG, "Receiving file completed for " + fileInfo.mFileName);
                 }
+                BTOppUtils.throughputInKbps(fileInfo.mLength, beginTime);
                 status = BluetoothShare.STATUS_SUCCESS;
             } else {
                 if (D) {
@@ -568,7 +587,7 @@
         if (D) {
             Log.d(TAG, "onConnect");
         }
-        if (V) {
+        if (D) {
             Constants.logHeader(request);
         }
         Long objectCount = null;
@@ -586,13 +605,13 @@
             Log.e(TAG, e.toString());
             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
         }
-        String destination;
         if (mTransport instanceof BluetoothObexTransport) {
             destination = ((BluetoothObexTransport) mTransport).getRemoteAddress();
         } else {
             destination = "FF:FF:FF:00:00:00";
         }
-        boolean isHandover = BluetoothOppManager.getInstance(mContext).isWhitelisted(destination);
+        isHandover = BluetoothOppManager.getInstance(mContext).isWhitelisted(destination);
+        if (D) Log.d(TAG, "isHandover :" + isHandover);
         if (isHandover) {
             // Notify the handover requester file transfer has started
             Intent intent = new Intent(Constants.ACTION_HANDOVER_STARTED);
@@ -623,7 +642,9 @@
     }
 
     private synchronized void releaseWakeLocks() {
+        BTOppUtils.releaseFullWakeLock();
         if (mPartialWakeLock.isHeld()) {
+            if (D) Log.d(TAG, "releasing partial wakelock");
             mPartialWakeLock.release();
         }
     }
@@ -631,7 +652,10 @@
     @Override
     public void onClose() {
         if (D) {
-            Log.d(TAG, "onClose");
+            Log.d(TAG, "onClose isHandover :" + isHandover);
+        }
+        if (isHandover) {
+            BluetoothOppManager.getInstance(mContext).removeWhitelist(destination);
         }
         releaseWakeLocks();
         mBluetoothOppService.acceptNewConnections();
diff --git a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
index 73d0194..edcbedf 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
@@ -58,10 +58,11 @@
     @Override
     public void onReceive(Context context, Intent intent) {
         String action = intent.getAction();
-
+        if(D) Log.d(TAG, "Action :" + action);
+        if (action == null) return;
         if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) {
             BluetoothOppManager mOppManager = BluetoothOppManager.getInstance(context);
-
+            BluetoothOppManager.isReadyForFileSharing = false;
             BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
 
             if (D) {
@@ -70,6 +71,7 @@
 
             if (remoteDevice == null) {
                 mOppManager.cleanUpSendingFileInfo();
+                BluetoothOppManager.isReadyForFileSharing = true;
                 return;
             }
             // Insert transfer session record to database
@@ -85,6 +87,7 @@
             } else {
                 toastMsg = context.getString(R.string.bt_toast_4, deviceName);
             }
+            BluetoothOppManager.isReadyForFileSharing = true;
             Toast.makeText(context, toastMsg, Toast.LENGTH_SHORT).show();
         } else if (action.equals(Constants.ACTION_INCOMING_FILE_CONFIRM)) {
             if (V) {
diff --git a/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java b/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
index 6d7fe95..7130418 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
@@ -36,6 +36,7 @@
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
+import android.database.CursorWindowAllocationException;
 import android.database.sqlite.SQLiteException;
 import android.net.Uri;
 import android.provider.OpenableColumns;
@@ -119,9 +120,12 @@
             } catch (SQLiteException e) {
                 // some content providers don't support the DISPLAY_NAME or SIZE columns
                 metadataCursor = null;
-            } catch (SecurityException e) {
+            } catch (SecurityException | NullPointerException e) {
                 Log.e(TAG, "generateFileInfo: Permission error, could not access URI: " + uri);
                 return SEND_FILE_INFO_ERROR;
+            } catch (CursorWindowAllocationException e) {
+                Log.e(TAG, " generateFileInfo :" + e);
+                throw new IllegalArgumentException(e.toString());
             }
 
             if (metadataCursor != null) {
@@ -175,6 +179,10 @@
                 // As a second source of getting the correct file length,
                 // get a file descriptor and get the stat length
                 AssetFileDescriptor fd = contentResolver.openAssetFileDescriptor(uri, "r");
+                if (fd == null) {
+                    Log.e(TAG, "fd is NULL");
+                    throw new IllegalArgumentException("Memory related error");
+                }
                 long statLength = fd.getLength();
                 if (length != statLength && statLength > 0) {
                     Log.e(TAG, "Content provider length is wrong (" + Long.toString(length)
@@ -206,6 +214,9 @@
                 }
             } catch (FileNotFoundException e) {
                 // Ignore
+            } catch (SecurityException | IllegalStateException e) {
+                Log.e(TAG, "Error generateFileInfo ", e);
+                return SEND_FILE_INFO_ERROR;
             }
         }
 
@@ -224,6 +235,9 @@
                 return SEND_FILE_INFO_ERROR;
             } catch (IOException e) {
                 return SEND_FILE_INFO_ERROR;
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Error generateFileInfo ", e);
+                return SEND_FILE_INFO_ERROR;
             }
         }
 
diff --git a/src/com/android/bluetooth/opp/BluetoothOppService.java b/src/com/android/bluetooth/opp/BluetoothOppService.java
index 383a497..f432635 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -115,6 +115,8 @@
 
     private UpdateThread mUpdateThread;
 
+    private boolean mUpdateThreadRunning;
+
     private ArrayList<BluetoothOppShareInfo> mShares;
 
     private ArrayList<BluetoothOppBatch> mBatches;
@@ -191,7 +193,7 @@
 
     @Override
     protected void create() {
-        if (V) {
+        if (D) {
             Log.v(TAG, "onCreate");
         }
         mShares = Lists.newArrayList();
@@ -206,12 +208,13 @@
         }.start();
 
         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+        BTOppUtils.addA2dpFilter(filter);
         registerReceiver(mBluetoothReceiver, filter);
 
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         synchronized (BluetoothOppService.this) {
             if (mAdapter == null) {
-                Log.w(TAG, "Local BT device is not enabled");
+                Log.w(TAG, "Local BT is not enabled");
             }
         }
         if (V) {
@@ -226,7 +229,7 @@
 
     @Override
     public boolean start() {
-        if (V) {
+        if (D) {
             Log.v(TAG, "start()");
         }
         mObserver = new BluetoothShareContentObserver();
@@ -313,6 +316,7 @@
     private Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
+            Log.i(TAG, " handleMessage :" + msg.what);
             switch (msg.what) {
                 case STOP_LISTENER:
                     stopListeners();
@@ -330,8 +334,19 @@
                     unregisterReceivers();
                     synchronized (BluetoothOppService.this) {
                         if (mUpdateThread != null) {
+                            mUpdateThread.interrupt();
+                        }
+                    }
+                    while (mUpdateThread != null && mUpdateThreadRunning) {
+                        try {
+                            Thread.sleep(50);
+                        } catch (Exception e) {
+                            Log.e(TAG, "Thread sleep", e);
+                        }
+                    }
+                    synchronized (BluetoothOppService.this) {
+                        if (mUpdateThread != null) {
                             try {
-                                mUpdateThread.interrupt();
                                 mUpdateThread.join();
                             } catch (InterruptedException e) {
                                 Log.e(TAG, "Interrupted", e);
@@ -339,7 +354,16 @@
                             mUpdateThread = null;
                         }
                     }
-                    mNotifier.cancelNotifications();
+                    if (D) Log.d(TAG," clear batches");
+                    if (mBatches != null) {
+                        mBatches.clear();
+                    }
+                    if (mShares != null) {
+                        mShares.clear();
+                    }
+                    if (mNotifier != null) {
+                        mNotifier.cancelNotifications();
+                    }
                     break;
                 case START_LISTENER:
                     if (mAdapter.isEnabled()) {
@@ -445,7 +469,8 @@
             Log.d(TAG, "start Socket Listeners");
         }
         stopListeners();
-        mServerSocket = ObexServerSockets.createInsecure(this);
+        mServerSocket = ObexServerSockets.createInsecureWithFixedChannels(this,
+                SdpManager.OPP_RFCOMM_CHANNEL, SdpManager.OPP_L2CAP_PSM);
         acceptNewConnections();
         SdpManager sdpManager = SdpManager.getDefaultManager();
         if (sdpManager == null || mServerSocket == null) {
@@ -463,16 +488,10 @@
 
     @Override
     protected void cleanup() {
-        if (V) {
+        if (D) {
             Log.v(TAG, "onDestroy");
         }
         stopListeners();
-        if (mBatches != null) {
-            mBatches.clear();
-        }
-        if (mShares != null) {
-            mShares.clear();
-        }
         if (mHandler != null) {
             mHandler.removeCallbacksAndMessages(null);
         }
@@ -484,9 +503,13 @@
                 getContentResolver().unregisterContentObserver(mObserver);
                 mObserver = null;
             }
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "unregisterContentObserver " + e.toString());
+        }
+        try {
             unregisterReceiver(mBluetoothReceiver);
         } catch (IllegalArgumentException e) {
-            Log.w(TAG, "unregisterReceivers " + e.toString());
+            Log.w(TAG, "unregisterReceiver " + e.toString());
         }
     }
 
@@ -504,7 +527,8 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-
+            if (D) Log.d(TAG, "action : " + action);
+            if (action == null) return;
             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                 switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
                     case BluetoothAdapter.STATE_ON:
@@ -541,6 +565,8 @@
                         mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
                         break;
                 }
+            } else {
+                BTOppUtils.checkAction(intent);
             }
         }
     };
@@ -551,6 +577,7 @@
             if (mUpdateThread == null) {
                 mUpdateThread = new UpdateThread();
                 mUpdateThread.start();
+                mUpdateThreadRunning = true;
             }
         }
     }
@@ -580,6 +607,7 @@
             while (!mIsInterrupted) {
                 synchronized (BluetoothOppService.this) {
                     if (mUpdateThread != this) {
+                        mUpdateThreadRunning = false;
                         throw new IllegalStateException(
                                 "multiple UpdateThreads in BluetoothOppService");
                     }
@@ -589,6 +617,7 @@
                     }
                     if (!mPendingUpdate) {
                         mUpdateThread = null;
+                        mUpdateThreadRunning = false;
                         return;
                     }
                     mPendingUpdate = false;
@@ -598,6 +627,7 @@
                                 BluetoothShare._ID);
 
                 if (cursor == null) {
+                    mUpdateThreadRunning = false;
                     return;
                 }
 
@@ -646,6 +676,9 @@
                             if (V) {
                                 Log.v(TAG, "Array update: inserting " + id + " @ " + arrayPos);
                             }
+                            if (shouldScanFile(arrayPos)) {
+                                scanFile(arrayPos);
+                            }
                             ++arrayPos;
                             cursor.moveToNext();
                             isAfterLast = cursor.isAfterLast();
@@ -668,6 +701,9 @@
                                 // This cursor row already exists in the stored array.
                                 updateShare(cursor, arrayPos);
 
+                                if (shouldScanFile(arrayPos)) {
+                                    scanFile(arrayPos);
+                                }
                                 ++arrayPos;
                                 cursor.moveToNext();
                                 isAfterLast = cursor.isAfterLast();
@@ -679,6 +715,9 @@
                                 }
                                 insertShare(cursor, arrayPos);
 
+                                if (shouldScanFile(arrayPos)) {
+                                    scanFile(arrayPos);
+                                }
                                 ++arrayPos;
                                 cursor.moveToNext();
                                 isAfterLast = cursor.isAfterLast();
@@ -691,6 +730,8 @@
 
                 cursor.close();
             }
+
+            mUpdateThreadRunning = false;
         }
     }
 
@@ -1062,7 +1103,7 @@
         if (V) {
             Log.v(TAG, "Deleted shares, number = " + delNum);
         }
-
+        BTOppUtils.cleanOnPowerOff(contentResolver);
         // Keep the latest inbound and successful shares.
         Cursor cursor =
                 contentResolver.query(BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID},
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
index 91af9ca..7bfc1ca 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
@@ -123,7 +123,7 @@
                     }
                     if ((device.equals(mBatch.mDestination)) && (mCurrentShare.mConfirm
                             == BluetoothShare.USER_CONFIRMATION_PENDING)) {
-                        if (V) {
+                        if (D) {
                             Log.v(TAG, "ACTION_ACL_DISCONNECTED to be processed for batch: "
                                     + mBatch.mId);
                         }
@@ -203,6 +203,7 @@
 
         @Override
         public void handleMessage(Message msg) {
+            Log.i(TAG, " handleMessage :" + msg.what);
             switch (msg.what) {
                 case SOCKET_ERROR_RETRY:
                     mConnectThread = new SocketConnectThread((BluetoothDevice) msg.obj, true);
@@ -217,7 +218,7 @@
                     if (V) {
                         Log.v(TAG, "receive TRANSPORT_ERROR msg");
                     }
-                    mConnectThread = null;
+
                     markBatchFailed(BluetoothShare.STATUS_CONNECTION_ERROR);
                     mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
 
@@ -230,7 +231,7 @@
                     if (V) {
                         Log.v(TAG, "Transfer receive TRANSPORT_CONNECTED msg");
                     }
-                    mConnectThread = null;
+
                     mTransport = (ObexTransport) msg.obj;
                     startObexSession();
 
@@ -502,7 +503,7 @@
      * Stop the transfer
      */
     public void stop() {
-        if (V) {
+        if (D) {
             Log.v(TAG, "stop");
         }
         if (mSession != null) {
@@ -513,19 +514,22 @@
         }
 
         cleanUp();
-        if (mConnectThread != null) {
-            try {
-                mConnectThread.interrupt();
-                if (V) {
-                    Log.v(TAG, "waiting for connect thread to terminate");
+        synchronized (this) {
+            if (mConnectThread != null) {
+                try {
+                    mConnectThread.interrupt();
+                    if (D) {
+                        Log.v(TAG, "waiting for connect thread to terminate");
+                    }
+                    mConnectThread.join();
+                } catch (InterruptedException e) {
+                    if (V) {
+                        Log.v(TAG, "Interrupted waiting for connect thread to join");
+                    }
                 }
-                mConnectThread.join();
-            } catch (InterruptedException e) {
-                if (V) {
-                    Log.v(TAG, "Interrupted waiting for connect thread to join");
-                }
+                mConnectThread = null;
+                if (D) Log.d(TAG, "mConnectThread terminated");
             }
-            mConnectThread = null;
         }
         // Prevent concurrent access
         synchronized (this) {
@@ -731,7 +735,7 @@
             try {
                 mBtSocket.connect();
 
-                if (V) {
+                if (D) {
                     Log.v(TAG,
                             "Rfcomm socket connection attempt took " + (System.currentTimeMillis()
                                     - mTimestamp) + " ms");
@@ -799,7 +803,7 @@
             }
             try {
                 mBtSocket.connect();
-                if (V) {
+                if (D) {
                     Log.v(TAG, "L2cap socket connection attempt took " + (System.currentTimeMillis()
                             - mTimestamp) + " ms");
                 }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index 1b5cd59..cb506d7 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -43,6 +43,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
+import android.database.SQLException;
 import android.net.Uri;
 import android.os.Environment;
 import android.util.Log;
@@ -76,17 +77,22 @@
 
     public static BluetoothOppTransferInfo queryRecord(Context context, Uri uri) {
         BluetoothOppTransferInfo info = new BluetoothOppTransferInfo();
-        Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
-        if (cursor != null) {
-            if (cursor.moveToFirst()) {
-                fillRecord(context, cursor, info);
+        try {
+            Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
+            if (cursor != null) {
+                if (cursor.moveToFirst()) {
+                    fillRecord(context, cursor, info);
+                }
+                cursor.close();
+            } else {
+                info = null;
+                if (V) {
+                    Log.v(TAG, "BluetoothOppManager Error: not got data from db for uri:" + uri);
+                }
             }
-            cursor.close();
-        } else {
+        } catch (SQLException | NullPointerException e) {
             info = null;
-            if (V) {
-                Log.v(TAG, "BluetoothOppManager Error: not got data from db for uri:" + uri);
-            }
+            Log.e(TAG, "queryRecord Error: ", e);
         }
         return info;
     }
@@ -204,8 +210,14 @@
             return;
         }
 
-        Uri path = BluetoothOppFileProvider.getUriForFile(context,
-                "com.android.bluetooth.opp.fileprovider", f);
+        Uri path = null;
+        try {
+            path = BluetoothOppFileProvider.getUriForFile(context,
+                    "com.android.bluetooth.opp.fileprovider", f);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Not able to find root path:" + f.getAbsolutePath());
+        }
+
         if (path == null) {
             Log.w(TAG, "Cannot get content URI for the shared file");
             return;
diff --git a/src/com/android/bluetooth/opp/Constants.java b/src/com/android/bluetooth/opp/Constants.java
index 3bf6cde..0dd612c 100644
--- a/src/com/android/bluetooth/opp/Constants.java
+++ b/src/com/android/bluetooth/opp/Constants.java
@@ -194,6 +194,9 @@
             "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
             "application/vnd.openxmlformats-officedocument.presentationml.presentation",
             "application/x-hwp",
+            "application/ogg",
+            "application/vnd.android.package-archive",
+            "text/comma-separated-values",
     };
 
     /** Where we store received files */
@@ -204,7 +207,7 @@
 
     static final boolean DEBUG = true;
 
-    static final boolean VERBOSE = false;
+    static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);;
 
     static final int MAX_RECORDS_IN_DATABASE = 50;
 
diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java
index 700b395..a7d682f 100644
--- a/src/com/android/bluetooth/pan/PanService.java
+++ b/src/com/android/bluetooth/pan/PanService.java
@@ -54,7 +54,8 @@
  */
 public class PanService extends ProfileService {
     private static final String TAG = "PanService";
-    private static final boolean DBG = false;
+    private static final String LOG_TAG = "BluetoothPan";
+    private static final boolean DBG = Log.isLoggable(LOG_TAG, Log.DEBUG);
     private static PanService sPanService;
 
     private static final String BLUETOOTH_IFACE_ADDR_START = "192.168.44.1";
@@ -323,6 +324,11 @@
             Log.e(TAG, "Pan Device not disconnected: " + device);
             return false;
         }
+        /* Cancel discovery while initiating PANU connection, if It's in progress */
+        if (mAdapter != null && mAdapter.isDiscovering()) {
+            Log.d(TAG,"Inquiry is going on, Cancelling inquiry while initiating PANU connection");
+            mAdapter.cancelDiscovery();
+        }
         Message msg = mHandler.obtainMessage(MESSAGE_CONNECT, device);
         mHandler.sendMessage(msg);
         return true;
@@ -501,6 +507,10 @@
                     + ", state: " + state + ", localRole:" + localRole + ", remoteRole:"
                     + remoteRole);
         }
+        if (device == null) {
+            Log.d(TAG, "BluetoothDevice is null, Ignoring state change ");
+            return;
+        }
         int prevState;
 
         BluetoothPanDevice panDevice = mPanDevices.get(device);
@@ -522,8 +532,9 @@
         // connect call will put us in STATE_DISCONNECTED. Then, the disconnect completes and
         // changes the state to STATE_DISCONNECTING. All future calls to BluetoothPan#connect
         // will fail until the caller explicitly calls BluetoothPan#disconnect.
-        if (prevState == BluetoothProfile.STATE_DISCONNECTED
-                && state == BluetoothProfile.STATE_DISCONNECTING) {
+        if (prevState == BluetoothProfile.STATE_DISCONNECTED &&
+            (state == BluetoothProfile.STATE_DISCONNECTING ||
+             state == BluetoothProfile.STATE_DISCONNECTED)) {
             Log.d(TAG, "Ignoring state change from " + prevState + " to " + state);
             mPanDevices.remove(device);
             return;
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
index f709cd5..5468a76 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
@@ -109,7 +109,7 @@
         Intent i = getIntent();
         String action = i.getAction();
         mDevice = i.getParcelableExtra(BluetoothPbapService.EXTRA_DEVICE);
-        if (action.equals(BluetoothPbapService.AUTH_CHALL_ACTION)) {
+        if (action != null && action.equals(BluetoothPbapService.AUTH_CHALL_ACTION)) {
             showPbapDialog(DIALOG_YES_NO_AUTH);
             mCurrentDialog = DIALOG_YES_NO_AUTH;
         } else {
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
index 247a76d..e78fe20 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
@@ -58,7 +58,9 @@
 import javax.obex.ResponseCodes;
 import javax.obex.ServerRequestHandler;
 
-public class BluetoothPbapObexServer extends ServerRequestHandler {
+import com.android.bluetooth.pbap.BluetoothPbapSimVcardManager.SimPaths;
+
+public class BluetoothPbapObexServer extends ServerRequestHandler implements SimPaths{
 
     private static final String TAG = "BluetoothPbapObexServer";
 
@@ -93,22 +95,12 @@
             0x66
     };
 
-    // Currently not support SIM card
     private static final String[] LEGAL_PATH = {
             "/telecom",
             "/telecom/pb",
             "/telecom/ich",
             "/telecom/och",
             "/telecom/mch",
-            "/telecom/cch"
-    };
-
-    @SuppressWarnings("unused") private static final String[] LEGAL_PATH_WITH_SIM = {
-            "/telecom",
-            "/telecom/pb",
-            "/telecom/ich",
-            "/telecom/och",
-            "/telecom/mch",
             "/telecom/cch",
             "/SIM1",
             "/SIM1/telecom",
@@ -202,6 +194,8 @@
 
     private PbapStateMachine mStateMachine;
 
+    protected static BluetoothPbapSimVcardManager mVcardSimManager;
+
     public static class ContentType {
         public static final int PHONEBOOK = 1;
 
@@ -212,6 +206,8 @@
         public static final int MISSED_CALL_HISTORY = 4;
 
         public static final int COMBINED_CALL_HISTORY = 5;
+
+        public static final int SIM_PHONEBOOK = 6;
     }
 
     public BluetoothPbapObexServer(Handler callback, Context context,
@@ -221,6 +217,8 @@
         mContext = context;
         mVcardManager = new BluetoothPbapVcardManager(mContext);
         mStateMachine = stateMachine;
+        mVcardSimManager = new BluetoothPbapSimVcardManager(mContext);
+        BluetoothPbapFixes.getFeatureSupport(mContext);
     }
 
     @Override
@@ -433,6 +431,12 @@
             validName = false;
         }
 
+        if (!BluetoothPbapFixes.isSimSupported && ((mCurrentPath.contains("SIM") ||
+                                  (validName && name.contains("SIM"))))) {
+            if (D) Log.d(TAG, "SIM support disabled ");
+            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+        }
+
         if (!validName || (validName && type.equals(TYPE_VCARD))) {
             if (D) {
                 Log.d(TAG,
@@ -441,21 +445,23 @@
 
             if (mCurrentPath.equals(PB_PATH)) {
                 appParamValue.needTag = ContentType.PHONEBOOK;
-            } else if (mCurrentPath.equals(ICH_PATH)) {
+            } else if (mCurrentPath.equals(ICH_PATH)|| mCurrentPath.equals(SIM_ICH_PATH)) {
                 appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
-            } else if (mCurrentPath.equals(OCH_PATH)) {
+            } else if (mCurrentPath.equals(OCH_PATH)|| mCurrentPath.equals(SIM_OCH_PATH)) {
                 appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY;
-            } else if (mCurrentPath.equals(MCH_PATH)) {
+            } else if (mCurrentPath.equals(MCH_PATH)|| mCurrentPath.equals(SIM_MCH_PATH)) {
                 appParamValue.needTag = ContentType.MISSED_CALL_HISTORY;
                 mNeedNewMissedCallsNum = true;
-            } else if (mCurrentPath.equals(CCH_PATH)) {
+            } else if (mCurrentPath.equals(CCH_PATH)|| mCurrentPath.equals(SIM_CCH_PATH)) {
                 appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY;
-            } else if (mCurrentPath.equals(TELECOM_PATH)) {
+            } else if (mCurrentPath.equals(TELECOM_PATH)|| mCurrentPath.equals(SIM_PATH)) {
                 /* PBAP 1.1.1 change */
                 if (!validName && type.equals(TYPE_LISTING)) {
                     Log.e(TAG, "invalid vcard listing request in default folder");
                     return ResponseCodes.OBEX_HTTP_NOT_FOUND;
                 }
+            } else if (mCurrentPath.equals(SIM_PB_PATH)) {
+                appParamValue.needTag = ContentType.SIM_PHONEBOOK;
             } else {
                 Log.w(TAG, "mCurrentpath is not valid path!!!");
                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
@@ -464,16 +470,14 @@
                 Log.v(TAG, "onGet(): appParamValue.needTag=" + appParamValue.needTag);
             }
         } else {
-            // Not support SIM card currently
-            if (name.contains(SIM1.subSequence(0, SIM1.length()))) {
-                Log.w(TAG, "Not support access SIM card info!");
-                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
-            }
-
             // we have weak name checking here to provide better
             // compatibility with other devices,although unique name such as
             // "pb.vcf" is required by SIG spec.
-            if (isNameMatchTarget(name, PB)) {
+            if (mVcardSimManager.isSimPhoneBook(name, type, PB, SIM1,
+                TYPE_PB, TYPE_LISTING, mCurrentPath)) {
+                appParamValue.needTag = ContentType.SIM_PHONEBOOK;
+                if (D) Log.d(TAG, "download SIM phonebook request");
+            } else if (isNameMatchTarget(name, PB)) {
                 appParamValue.needTag = ContentType.PHONEBOOK;
                 if (D) {
                     Log.v(TAG, "download phonebook request");
@@ -570,7 +574,7 @@
         return false;
     }
 
-    private class AppParamValue {
+    class AppParamValue {
         public int maxListCount;
 
         public int listStartOffset;
@@ -746,25 +750,36 @@
             int size) {
         StringBuilder result = new StringBuilder();
         int itemsFound = 0;
+        String type = "";
         result.append("<?xml version=\"1.0\"?>");
         result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">");
         result.append("<vCard-listing version=\"1.0\">");
 
         // Phonebook listing request
         if (appParamValue.needTag == ContentType.PHONEBOOK) {
-            String type = "";
             if (appParamValue.searchAttr.equals("0")) {
                 type = "name";
             } else if (appParamValue.searchAttr.equals("1")) {
                 type = "number";
             }
             if (type.length() > 0) {
-                itemsFound = createList(appParamValue, needSendBody, size, result, type);
+                itemsFound = BluetoothPbapFixes.createList(mVcardSimManager, mVcardManager,
+                    this, mVcardSelector, mOrderBy, appParamValue, needSendBody, size, result,
+                    type);
             } else {
                 return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
             }
+        // SIM Phonebook listing Request
+        } else if (appParamValue.needTag == ContentType.SIM_PHONEBOOK) {
+            type = mVcardSimManager.getType(appParamValue.searchAttr);
+            if (type.length() > 0) {
+                itemsFound = BluetoothPbapFixes.createList(mVcardSimManager, mVcardManager,
+                    this,mVcardSelector,mOrderBy,appParamValue, needSendBody, size, result, type);
+            } else {
+                return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+            }
+        // Call history listing request
         } else {
-            // Call history listing request
             ArrayList<String> nameList = mVcardManager.loadCallHistoryList(appParamValue.needTag);
             int requestSize =
                     nameList.size() >= appParamValue.maxListCount ? appParamValue.maxListCount
@@ -896,7 +911,7 @@
     }
 
     /** Function to send vcard data to client */
-    private int pushBytes(Operation op, final String vcardString) {
+    protected int pushBytes(Operation op, final String vcardString) {
         if (vcardString == null) {
             Log.w(TAG, "vcardString is null!");
             return ResponseCodes.OBEX_HTTP_OK;
@@ -971,6 +986,8 @@
 
                 nmnum = nmnum > 0 ? nmnum : 0;
                 misnum[0] = (byte) nmnum;
+                ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
+                        ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
                 if (D) {
                     Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
                             + nmnum);
@@ -980,7 +997,7 @@
             if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
                 setDbCounters(ap);
             }
-            if (needSendPhonebookVersionCounters) {
+            if (BluetoothPbapFixes.isSupportedPbap12 && needSendPhonebookVersionCounters) {
                 setFolderVersionCounters(ap);
             }
             if (needSendCallHistoryVersionCounters) {
@@ -1042,7 +1059,8 @@
             }
         }
 
-        if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
+        if (BluetoothPbapFixes.isSupportedPbap12
+               && checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
             setDbCounters(ap);
             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
             try {
@@ -1053,7 +1071,7 @@
             }
         }
 
-        if (needSendPhonebookVersionCounters) {
+        if (BluetoothPbapFixes.isSupportedPbap12 && needSendPhonebookVersionCounters) {
             setFolderVersionCounters(ap);
             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
             try {
@@ -1158,6 +1176,7 @@
         if (strIndex.trim().length() != 0) {
             try {
                 intIndex = Integer.parseInt(strIndex);
+                if (D) Log.d(TAG, "Index: " + intIndex + "orderby: " + mOrderBy);
             } catch (NumberFormatException e) {
                 Log.e(TAG, "catch number format exception " + e.toString());
                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
@@ -1178,7 +1197,7 @@
             Log.w(TAG, "wrong path!");
             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
         } else if (appParamValue.needTag == ContentType.PHONEBOOK) {
-            if (intIndex < 0 || intIndex >= size) {
+            if (intIndex < 0 || !BluetoothPbapFixes.checkContactsVcardId(intIndex, mContext)) {
                 Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
                 return ResponseCodes.OBEX_HTTP_NOT_FOUND;
             } else if (intIndex == 0) {
@@ -1190,6 +1209,10 @@
                 return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
                         mOrderBy, appParamValue.ignorefilter, appParamValue.propertySelector);
             }
+        } else if (appParamValue.needTag == ContentType.SIM_PHONEBOOK) {
+            return mVcardSimManager.initiatePullSimVcardEntry(intIndex, size,
+                    vcard21, mOrderBy, name, op, mVcardManager, appParamValue.ignorefilter,
+                    appParamValue.propertySelector, this);
         } else {
             if (intIndex <= 0 || intIndex > size) {
                 Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
@@ -1244,7 +1267,8 @@
         }
 
         // Limit the number of call log to CALLLOG_NUM_LIMIT
-        if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) {
+        if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK
+                && (appParamValue.needTag != BluetoothPbapObexServer.ContentType.SIM_PHONEBOOK)) {
             if (requestSize > CALLLOG_NUM_LIMIT) {
                 requestSize = CALLLOG_NUM_LIMIT;
             }
@@ -1278,6 +1302,10 @@
                         appParamValue.propertySelector, appParamValue.vCardSelector,
                         appParamValue.vCardSelectorOperator, mVcardSelector);
             }
+        } else if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.SIM_PHONEBOOK) {
+            return mVcardSimManager.initiatePullSimPhonebook(startPoint,
+                    endPoint, vcard21, op, mVcardManager, appParamValue.ignorefilter,
+                    appParamValue.propertySelector, this);
         } else {
             return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op,
                     startPoint + 1, endPoint + 1, vcard21, needSendBody, pbSize,
@@ -1366,7 +1394,7 @@
         }
     }
 
-    private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) {
+    protected void writeVCardEntry(int vcfIndex, String name, StringBuilder result) {
         result.append("<card handle=\"");
         result.append(vcfIndex);
         result.append(".vcf\" name=\"");
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
index 374b5a1..66ef200 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -36,6 +36,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothSocket;
+import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothPbap;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -47,6 +48,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelUuid;
 import android.os.PowerManager;
 import android.os.UserManager;
 import android.support.annotation.VisibleForTesting;
@@ -68,17 +70,18 @@
 
 public class BluetoothPbapService extends ProfileService implements IObexConnectionHandler {
     private static final String TAG = "BluetoothPbapService";
+    private static final String LOG_TAG = "BluetoothPbap";
 
     /**
      * To enable PBAP DEBUG/VERBOSE logging - run below cmd in adb shell, and
      * restart com.android.bluetooth process. only enable DEBUG log:
      * "setprop log.tag.BluetoothPbapService DEBUG"; enable both VERBOSE and
-     * DEBUG log: "setprop log.tag.BluetoothPbapService VERBOSE"
+     * DEBUG log: "setprop log.tag.BluetoothPbap VERBOSE"
      */
 
     public static final boolean DEBUG = true;
 
-    public static final boolean VERBOSE = false;
+    public static final boolean VERBOSE = Log.isLoggable(LOG_TAG, Log.VERBOSE);
 
     /**
      * Intent indicating incoming obex authentication request which is from
@@ -127,9 +130,12 @@
     static final int CONTACTS_LOADED = 5;
     static final int CHECK_SECONDARY_VERSION_COUNTER = 6;
     static final int ROLLOVER_COUNTERS = 7;
+    static final int GET_LOCAL_TELEPHONY_DETAILS = 8;
+    static final int CLEANUP_HANDLER_TASKS = 9;
 
     static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
     static final int RELEASE_WAKE_LOCK_DELAY = 10000;
+    static final int CLEANUP_HANDLER_DELAY = 50;
 
     private PowerManager.WakeLock mWakeLock;
 
@@ -147,7 +153,7 @@
     private static final int PBAP_NOTIFICATION_ID_START = 1000000;
     private static final int PBAP_NOTIFICATION_ID_END = 2000000;
 
-    private int mSdpHandle = -1;
+    protected int mSdpHandle = -1;
 
     protected Context mContext;
 
@@ -219,7 +225,7 @@
                 if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
                     if (savePreference) {
                         device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
-                        if (VERBOSE) {
+                        if (DEBUG) {
                             Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)");
                         }
                     }
@@ -227,7 +233,7 @@
                 } else {
                     if (savePreference) {
                         device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
-                        if (VERBOSE) {
+                        if (DEBUG) {
                             Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)");
                         }
                     }
@@ -254,6 +260,43 @@
                 }
                 sm.sendMessage(PbapStateMachine.AUTH_CANCELLED);
             }
+        } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
+            int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+                    BluetoothDevice.ERROR);
+            BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            if (bondState == BluetoothDevice.BOND_BONDED && BluetoothPbapFixes.isSupportedPbap12) {
+                if (VERBOSE) Log.v(TAG, "Remote Device Type: " + remoteDevice.getType());
+                if (remoteDevice.getType() == BluetoothDevice.DEVICE_TYPE_CLASSIC ||
+                        remoteDevice.getType() == BluetoothDevice.DEVICE_TYPE_DUAL)
+                    remoteDevice.sdpSearch(BluetoothUuid.PBAP_PCE);
+            }
+        } else if (BluetoothDevice.ACTION_SDP_RECORD.equals(action)) {
+            ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
+            if (BluetoothUuid.PBAP_PCE.equals(uuid)) {
+                Log.d(TAG, "Received SDP Response for PCE UUID: " + uuid.toString());
+                BluetoothDevice remoteDevice = intent.getParcelableExtra(
+                        BluetoothDevice.EXTRA_DEVICE);
+                boolean hasPbap12Support =
+                        BluetoothPbapFixes.remoteSupportsPbap1_2(remoteDevice);
+                String isRebonded = BluetoothPbapFixes.PbapSdpResponse.get(
+                        remoteDevice.getAddress().substring(0,8));
+                Integer remoteVersion = BluetoothPbapFixes.remoteVersion.get(
+                        remoteDevice.getAddress().substring(0,8));
+                if (hasPbap12Support && (isRebonded.equals("N"))) {
+                    Log.d(TAG, "Remote Supports PBAP 1.2. Notify user");
+                    BluetoothPbapFixes.createNotification(this, true);
+                } else if (!hasPbap12Support
+                        && (remoteVersion != null &&
+                            remoteVersion.intValue() < BluetoothPbapFixes.PBAP_ADV_VERSION)
+                        && (isRebonded != null && isRebonded.equals("N"))) {
+                    Log.d(TAG, "Remote PBAP profile support downgraded");
+                    BluetoothPbapFixes.createNotification(this, false);
+                } else {
+                    Log.d(TAG, "Notification Not Required.");
+                    if (BluetoothPbapFixes.mNotificationManager != null)
+                        BluetoothPbapFixes.mNotificationManager.cancelAll();
+                }
+            }
         } else {
             Log.w(TAG, "Unhandled intent action: " + action);
         }
@@ -283,6 +326,16 @@
         if (mSessionStatusHandler != null) {
             mSessionStatusHandler.removeCallbacksAndMessages(null);
         }
+
+        mSessionStatusHandler.sendMessageDelayed(
+            mSessionStatusHandler.obtainMessage(CLEANUP_HANDLER_TASKS), CLEANUP_HANDLER_DELAY);
+    }
+
+    private void cleanUpHandlerTasks() {
+        Log.d(TAG, "quitHandlerThread");
+        if (mHandlerThread != null) {
+            mHandlerThread.quitSafely();
+        }
     }
 
     private void cleanUpServerSocket() {
@@ -305,11 +358,9 @@
         if (mSdpHandle > -1) {
             Log.w(TAG, "createSdpRecord, SDP record already created");
         }
-        mSdpHandle = SdpManager.getDefaultManager()
-                .createPbapPseRecord("OBEX Phonebook Access Server",
-                        mServerSockets.getRfcommChannel(), mServerSockets.getL2capPsm(),
-                        SDP_PBAP_SERVER_VERSION, SDP_PBAP_SUPPORTED_REPOSITORIES,
-                        SDP_PBAP_SUPPORTED_FEATURES);
+        BluetoothPbapFixes.getFeatureSupport(mContext);
+        BluetoothPbapFixes.createSdpRecord(mServerSockets, this);
+
         if (DEBUG) {
             Log.d(TAG, "created Sdp record, mSdpHandle=" + mSdpHandle);
         }
@@ -346,7 +397,9 @@
 
             switch (msg.what) {
                 case START_LISTENER:
-                    mServerSockets = ObexServerSockets.create(BluetoothPbapService.this);
+                    mServerSockets = ObexServerSockets.createWithFixedChannels
+                            (sBluetoothPbapService, SdpManager.PBAP_RFCOMM_CHANNEL,
+                            SdpManager.PBAP_L2CAP_PSM);
                     if (mServerSockets == null) {
                         Log.w(TAG, "ObexServerSockets.create() returned null");
                         break;
@@ -408,6 +461,12 @@
                         mPbapStateMachineMap.remove(remoteDevice);
                     }
                     break;
+                case GET_LOCAL_TELEPHONY_DETAILS:
+                    getLocalTelephonyDetails();
+                    break;
+                case CLEANUP_HANDLER_TASKS:
+                    cleanUpHandlerTasks();
+                    break;
                 default:
                     break;
             }
@@ -494,6 +553,8 @@
         filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
         filter.addAction(AUTH_RESPONSE_ACTION);
         filter.addAction(AUTH_CANCELLED_ACTION);
+        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
         BluetoothPbapConfig.init(this);
         registerReceiver(mPbapReceiver, filter);
         try {
@@ -505,20 +566,15 @@
             Log.e(TAG, "SQLite exception: " + e);
         } catch (IllegalStateException e) {
             Log.e(TAG, "Illegal state exception, content observer is already registered");
+        } catch (SecurityException e) {
+            Log.e(TAG, "Error while rigistering ContactChangeObserver " + e);
         }
 
-        TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
-        if (tm != null) {
-            sLocalPhoneNum = tm.getLine1Number();
-            sLocalPhoneName = tm.getLine1AlphaTag();
-            if (TextUtils.isEmpty(sLocalPhoneName)) {
-                sLocalPhoneName = this.getString(R.string.localPhoneName);
-            }
-        }
-
+        setBluetoothPbapService(this);
+        mSessionStatusHandler.sendMessage(
+                mSessionStatusHandler.obtainMessage(GET_LOCAL_TELEPHONY_DETAILS));
         mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(LOAD_CONTACTS));
         mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
-        setBluetoothPbapService(this);
         return true;
     }
 
@@ -531,9 +587,6 @@
         if (mSessionStatusHandler != null) {
             mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget();
         }
-        if (mHandlerThread != null) {
-            mHandlerThread.quitSafely();
-        }
         mContactsLoaded = false;
         if (mContactChangeObserver == null) {
             Log.i(TAG, "Avoid unregister when receiver it is not registered");
@@ -667,7 +720,10 @@
                     + " socket=" + socket);
             return false;
         }
-
+        if (getConnectedDevices().size() >= BluetoothPbapFixes.MAX_CONNECTED_DEVICES) {
+            Log.i(TAG, "Cannot connect to " + remoteDevice + " multiple devices connected already");
+            return false;
+        }
         PbapStateMachine sm = PbapStateMachine.make(this, mHandlerThread.getLooper(), remoteDevice,
                 socket,  this, mSessionStatusHandler, mNextNotificationId);
         mNextNotificationId++;
@@ -751,9 +807,14 @@
             Runnable r = new Runnable() {
                 @Override
                 public void run() {
-                    BluetoothPbapUtils.loadAllContacts(mContext,
-                            mSessionStatusHandler);
-                    mThreadLoadContacts = null;
+                    try {
+                        BluetoothPbapUtils.loadAllContacts(mContext,
+                                mSessionStatusHandler);
+                    } catch (Exception e) {
+                        Log.e(TAG, "loadAllContacts failed: " + e);
+                    } finally {
+                        mThreadLoadContacts = null;
+                    }
                 }
             };
             mThreadLoadContacts = new Thread(r);
@@ -766,13 +827,32 @@
             Runnable r = new Runnable() {
                 @Override
                 public void run() {
-                    BluetoothPbapUtils.updateSecondaryVersionCounter(mContext,
-                            mSessionStatusHandler);
-                    mThreadUpdateSecVersionCounter = null;
+                    try {
+                        BluetoothPbapUtils.updateSecondaryVersionCounter(mContext,
+                                mSessionStatusHandler);
+                    } catch (Exception e) {
+                        Log.e(TAG, "updateSecondaryVersion counter failed: " + e);
+                    } finally {
+                        mThreadUpdateSecVersionCounter = null;
+                    }
                 }
             };
             mThreadUpdateSecVersionCounter = new Thread(r);
             mThreadUpdateSecVersionCounter.start();
         }
     }
+
+    private void getLocalTelephonyDetails() {
+        TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+        if (tm != null) {
+            sLocalPhoneNum = tm.getLine1Number();
+            sLocalPhoneName = tm.getLine1AlphaTag();
+            if (TextUtils.isEmpty(sLocalPhoneName)) {
+                sLocalPhoneName = this.getString(R.string.localPhoneName);
+            }
+        }
+        if (VERBOSE)
+            Log.v(TAG, "Local Phone Details- Number:" + sLocalPhoneNum
+                    + ", Name:" + sLocalPhoneName);
+    }
 }
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
index f8b8e5f..a5c3ec3 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
@@ -21,6 +21,7 @@
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
 import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
 import android.net.Uri;
 import android.os.Handler;
 import android.preference.PreferenceManager;
@@ -199,7 +200,8 @@
         }
     }
 
-    static void loadAllContacts(Context context, Handler handler) {
+    static void loadAllContacts(Context context, Handler handler)
+            throws IllegalStateException, SQLiteException {
         if (V) {
             Log.v(TAG, "Loading Contacts ...");
         }
@@ -213,7 +215,8 @@
         handler.sendMessage(handler.obtainMessage(BluetoothPbapService.CONTACTS_LOADED));
     }
 
-    static void updateSecondaryVersionCounter(Context context, Handler handler) {
+    static void updateSecondaryVersionCounter(Context context, Handler handler)
+            throws IllegalStateException, SQLiteException {
             /* updatedList stores list of contacts which are added/updated after
              * the time when contacts were last updated. (contactsLastUpdated
              * indicates the time when contact/contacts were last updated and
@@ -229,9 +232,15 @@
             Log.d(TAG, "Failed to fetch data from contact database");
             return;
         }
+        int indexCid = c.getColumnIndex(Contacts._ID);
+        int indexTimestamp = c.getColumnIndex(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP);
         while (c.moveToNext()) {
-            String contactId = c.getString(0);
-            long lastUpdatedTime = c.getLong(1);
+            if (c.isNull(indexCid)) {
+                Log.d(TAG, "Skipping unavailable contact from cursor.");
+                continue;
+            }
+            String contactId = c.getString(indexCid);
+            long lastUpdatedTime = c.getLong(indexTimestamp);
             if (lastUpdatedTime > sContactsLastUpdated) {
                 updatedList.add(contactId);
             }
@@ -430,6 +439,10 @@
         int indexMimeType = c.getColumnIndex(Data.MIMETYPE);
         String contactId, data, mimeType;
         while (c.moveToNext()) {
+            if (c.isNull(indexCId) || c.isNull(indexMimeType)) {
+                Log.e(TAG, "Contact data in cursor is not found. Skipping.");
+                continue;
+            }
             contactId = c.getString(indexCId);
             data = c.getString(indexData);
             mimeType = c.getString(indexMimeType);
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
index e58fa2e..c118b18 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
@@ -83,6 +83,7 @@
     static final String[] PHONES_CONTACTS_PROJECTION = new String[]{
             Phone.CONTACT_ID, // 0
             Phone.DISPLAY_NAME, // 1
+            Phone.ACCOUNT_TYPE_AND_DATA_SET, //2
     };
 
     static final String[] PHONE_LOOKUP_PROJECTION = new String[]{
@@ -102,6 +103,7 @@
     static final String CALLLOG_SORT_ORDER = Calls._ID + " DESC";
 
     private static final int NEED_SEND_BODY = -1;
+    protected static boolean isPullVcardEntry = false;
 
     public BluetoothPbapVcardManager(final Context context) {
         mContext = context;
@@ -155,6 +157,9 @@
             case BluetoothPbapObexServer.ContentType.PHONEBOOK:
                 size = getContactsSize();
                 break;
+            case BluetoothPbapObexServer.ContentType.SIM_PHONEBOOK:
+                size = BluetoothPbapObexServer.mVcardSimManager.getSIMContactsSize();
+                break;
             default:
                 size = getCallHistorySize(type);
                 break;
@@ -168,19 +173,27 @@
     public final int getContactsSize() {
         final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
         Cursor contactCursor = null;
+        MatrixCursor mCursor = null;
         try {
-            contactCursor = mResolver.query(myUri, new String[]{Phone.CONTACT_ID}, null, null,
-                    Phone.CONTACT_ID);
+            contactCursor = mResolver.query(
+                    myUri,
+                    new String[] {Phone.CONTACT_ID, Phone.ACCOUNT_TYPE_AND_DATA_SET},
+                    null, null, Phone.CONTACT_ID);
             if (contactCursor == null) {
                 return 0;
             }
-            return getDistinctContactIdSize(contactCursor) + 1; // always has the 0.vcf
+            mCursor = BluetoothPbapFixes.filterOutSimContacts(contactCursor);
+            return mCursor.getCount() + 1; // always has the 0.vcf
         } catch (CursorWindowAllocationException e) {
             Log.e(TAG, "CursorWindowAllocationException while getting Contacts size");
         } finally {
             if (contactCursor != null) {
                 contactCursor.close();
             }
+            if (mCursor != null) {
+                mCursor.close();
+                mCursor = null;
+            }
         }
         return 0;
     }
@@ -260,7 +273,7 @@
         if (ownerName == null || ownerName.length() == 0) {
             ownerName = BluetoothPbapService.getLocalPhoneName();
         }
-        nameList.add(ownerName);
+        nameList.add(ownerName + "," + "0");
         //End enhancement
 
         final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
@@ -323,7 +336,7 @@
         if (ownerName == null || ownerName.length() == 0) {
             ownerName = BluetoothPbapService.getLocalPhoneName();
         }
-        nameList.add(ownerName);
+        nameList.add(ownerName + "," + "0");
         // End enhancement
 
         final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
@@ -332,12 +345,17 @@
             contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null,
                     Phone.CONTACT_ID);
 
+            ArrayList<String> contactNameIdList = new ArrayList<String>();
+            contactCursor = getContactNameIdList(contactCursor,
+                        contactNameIdList, mContext.getString(android.R.string.unknownName));
             if (contactCursor != null) {
                 if (!composer.initWithCallback(contactCursor,
                         new EnterpriseRawContactEntitlesInfoCallback())) {
                     return nameList;
                 }
 
+                int i = 0;
+                contactCursor.moveToFirst();
                 while (!composer.isAfterLast()) {
                     String vcard = composer.createOneEntry();
                     if (vcard == null) {
@@ -362,8 +380,9 @@
                         if (TextUtils.isEmpty(name)) {
                             name = mContext.getString(android.R.string.unknownName);
                         }
-                        nameList.add(name);
+                        nameList.add(contactNameIdList.get(i));
                     }
+                    i++;
                 }
                 if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
                     if (V) {
@@ -558,6 +577,7 @@
         try {
             contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null,
                     Phone.CONTACT_ID);
+            contactCursor = BluetoothPbapFixes.filterOutSimContacts(contactCursor);
             if (contactCursor != null) {
                 contactIdCursor =
                         ContactCursorFilter.filterByRange(contactCursor, startPoint, endPoint);
@@ -570,7 +590,7 @@
             }
         }
 
-        if (vcardselect) {
+        if (BluetoothPbapFixes.isSupportedPbap12 && vcardselect) {
             return composeContactsAndSendSelectedVCards(op, contactIdCursor, vcardType21,
                     ownerVCard, needSendBody, pbSize, ignorefilter, filter, vcardselector,
                     vcardselectorop);
@@ -603,6 +623,7 @@
         } catch (CursorWindowAllocationException e) {
             Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard");
         } finally {
+            contactCursor = BluetoothPbapFixes.filterOutSimContacts(contactCursor);
             if (contactCursor != null) {
                 contactIdCursor = ContactCursorFilter.filterByOffset(contactCursor, offset);
                 contactCursor.close();
@@ -624,6 +645,7 @@
          * @return a cursor containing contact id of {@code offset} contact.
          */
         public static Cursor filterByOffset(Cursor contactCursor, int offset) {
+            isPullVcardEntry = true;
             return filterByRange(contactCursor, offset, offset);
         }
 
@@ -644,6 +666,10 @@
             final MatrixCursor contactIdsCursor = new MatrixCursor(new String[]{
                     Phone.CONTACT_ID
             });
+            if (startPoint == endPoint && isPullVcardEntry) {
+                return BluetoothPbapFixes.getVcardEntry(contactCursor,
+                        contactIdsCursor, contactIdColumn, startPoint);
+            }
             while (contactCursor.moveToNext() && currentOffset <= endPoint) {
                 long currentContactId = contactCursor.getLong(contactIdColumn);
                 if (previousContactId != currentContactId) {
@@ -1315,17 +1341,21 @@
         final int contactIdColumn = cursor.getColumnIndex(Data.CONTACT_ID);
         final int idColumn = cursor.getColumnIndex(Data._ID);
         final int nameColumn = cursor.getColumnIndex(Data.DISPLAY_NAME);
+        final int accountIndex = cursor.getColumnIndex(Phone.ACCOUNT_TYPE_AND_DATA_SET);
         cursor.moveToPosition(-1);
         while (cursor.moveToNext()) {
             final long contactId =
                     cursor.getLong(contactIdColumn != -1 ? contactIdColumn : idColumn);
             String displayName = nameColumn != -1 ? cursor.getString(nameColumn) : defaultName;
+            String accountType = accountIndex != -1 ? cursor.getString(accountIndex) :
+                    BluetoothPbapFixes.getAccount(contactId);
             if (TextUtils.isEmpty(displayName)) {
                 displayName = defaultName;
             }
 
             String newString = displayName + "," + contactId;
-            if (!resultList.contains(newString)) {
+            if (!resultList.contains(newString) &&
+                    !(accountType != null && accountType.startsWith("com.android.sim"))) {
                 resultList.add(newString);
             }
         }
@@ -1335,4 +1365,36 @@
             }
         }
     }
+
+    /* creates name and id list of Non-sim contacts as display_name + "," + contact_id */
+    protected static Cursor getContactNameIdList(Cursor cursor,
+        ArrayList<String> contactIdList, String unknownName) {
+        if (cursor == null)
+            return null;
+        MatrixCursor mCursor = new MatrixCursor(new String[]{
+                    Phone.CONTACT_ID
+        });
+
+        long previousContactId = -1;
+        final int contactIdColumn = cursor.getColumnIndex(Data.CONTACT_ID);
+        final int idColumn = cursor.getColumnIndex(Data._ID);
+        final int nameColumn = cursor.getColumnIndex(Data.DISPLAY_NAME);
+        final int account_col_id = cursor.getColumnIndex(Phone.ACCOUNT_TYPE_AND_DATA_SET);
+        cursor.moveToPosition(-1);
+        while (cursor.moveToNext()) {
+             long currentContactId = contactIdColumn != -1 ? cursor.getLong(contactIdColumn)
+                    : cursor.getLong(idColumn);
+             String displayName = nameColumn != -1 ? cursor.getString(nameColumn)
+                    : unknownName;
+             String accType = cursor.getString(account_col_id);
+             if (previousContactId != currentContactId &&
+                    !(accType != null && accType.startsWith("com.android.sim"))) {
+                if (V) Log.v(TAG, displayName + "," + currentContactId);
+                previousContactId = currentContactId;
+                mCursor.addRow(new Long[]{currentContactId});
+                contactIdList.add(displayName + "," + Long.toString(currentContactId));
+             }
+        }
+        return mCursor;
+    }
 }
diff --git a/src/com/android/bluetooth/pbap/PbapStateMachine.java b/src/com/android/bluetooth/pbap/PbapStateMachine.java
index 0f53be5..c850d8c 100644
--- a/src/com/android/bluetooth/pbap/PbapStateMachine.java
+++ b/src/com/android/bluetooth/pbap/PbapStateMachine.java
@@ -342,6 +342,8 @@
             }
             BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
             mServerSession = new ServerSession(transport, mPbapServer, mObexAuth);
+            BluetoothPbapFixes.updateMtu(mServerSession, transport.isSrmSupported(),
+                    mConnSocket.getMaxReceivePacketSize());
             // It's ok to just use one wake lock
             // Message MSG_ACQUIRE_WAKE_LOCK is always surrounded by RELEASE. safe.
         }
@@ -392,9 +394,11 @@
                             .setFlag(Notification.FLAG_AUTO_CANCEL, true)
                             .setFlag(Notification.FLAG_ONLY_ALERT_ONCE, true)
                             .setContentIntent(
-                                    PendingIntent.getActivity(mService, 0, clickIntent, 0))
+                                    PendingIntent.getActivity(mService, 0, clickIntent,
+                                    PendingIntent.FLAG_UPDATE_CURRENT))
                             .setDeleteIntent(
-                                    PendingIntent.getBroadcast(mService, 0, deleteIntent, 0))
+                                    PendingIntent.getBroadcast(mService, 0, deleteIntent,
+                                    PendingIntent.FLAG_UPDATE_CURRENT))
                             .setLocalOnly(true)
                             .build();
             nm.notify(mNotificationId, notification);
diff --git a/src/com/android/bluetooth/sap/SapService.java b/src/com/android/bluetooth/sap/SapService.java
index 66e0b19..fea5002 100644
--- a/src/com/android/bluetooth/sap/SapService.java
+++ b/src/com/android/bluetooth/sap/SapService.java
@@ -42,9 +42,10 @@
 
     private static final String SDP_SAP_SERVICE_NAME = "SIM Access";
     private static final int SDP_SAP_VERSION = 0x0102;
+    private static final String LOG_TAG = "BluetoothSap";
     private static final String TAG = "SapService";
-    public static final boolean DEBUG = false;
-    public static final boolean VERBOSE = false;
+    public static final boolean DEBUG = true;
+    public static final boolean VERBOSE = Log.isLoggable(LOG_TAG, Log.VERBOSE);
 
     /* Message ID's */
     private static final int START_LISTENER = 1;
@@ -155,9 +156,9 @@
                 // It is mandatory for MSE to support initiation of bonding and encryption.
                 // TODO: Consider reusing the mServerSocket - it is indented to be reused
                 //       for multiple connections.
-                mServerSocket = mAdapter.listenUsingRfcommOn(
-                        BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, true, true);
                 removeSdpRecord();
+                mServerSocket = mAdapter.listenUsingRfcommOn(
+                        SdpManager.SAP_RFCOMM_CHANNEL, true, true);
                 mSdpHandle = SdpManager.getDefaultManager()
                         .createSapsRecord(SDP_SAP_SERVICE_NAME, mServerSocket.getChannel(),
                                 SDP_SAP_VERSION);
@@ -367,7 +368,7 @@
                     }
                     int permission = mRemoteDevice.getSimAccessPermission();
 
-                    if (VERBOSE) {
+                    if (DEBUG) {
                         Log.v(TAG, "getSimAccessPermission() = " + permission);
                     }
 
@@ -394,7 +395,7 @@
                         setUserTimeoutAlarm();
                         sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
 
-                        if (VERBOSE) {
+                        if (DEBUG) {
                             Log.v(TAG, "waiting for authorization for connection from: "
                                     + sRemoteDeviceName);
                         }
@@ -433,9 +434,7 @@
 
             switch (msg.what) {
                 case START_LISTENER:
-                    if (mAdapter.isEnabled()) {
-                        startRfcommSocketListener();
-                    }
+                    startRfcommSocketListener();
                     break;
                 case USER_TIMEOUT:
                     if (mIsWaitingAuthorization) {
@@ -621,7 +620,6 @@
         Log.v(TAG, "start()");
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
-        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         filter.addAction(USER_CONFIRM_TIMEOUT_ACTION);
 
@@ -763,25 +761,6 @@
                 Log.v(TAG, "onReceive");
             }
             String action = intent.getAction();
-            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-                int state =
-                        intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
-                if (state == BluetoothAdapter.STATE_TURNING_OFF) {
-                    if (DEBUG) {
-                        Log.d(TAG, "STATE_TURNING_OFF");
-                    }
-                    sendShutdownMessage();
-                } else if (state == BluetoothAdapter.STATE_ON) {
-                    if (DEBUG) {
-                        Log.d(TAG, "STATE_ON");
-                    }
-                    // start RFCOMM listener
-                    mSessionStatusHandler.sendMessage(
-                            mSessionStatusHandler.obtainMessage(START_LISTENER));
-                }
-                return;
-            }
-
             if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
                 Log.v(TAG, " - Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY");
                 if (!mIsWaitingAuthorization) {
diff --git a/src/com/android/bluetooth/sdp/SdpManager.java b/src/com/android/bluetooth/sdp/SdpManager.java
index d0cca25..340fac0 100644
--- a/src/com/android/bluetooth/sdp/SdpManager.java
+++ b/src/com/android/bluetooth/sdp/SdpManager.java
@@ -49,6 +49,21 @@
     public static final byte PBAP_REPO_SPEED_DAIL = 0x01 << 2;
     public static final byte PBAP_REPO_FAVORITES = 0x01 << 3;
 
+    public static final int OPP_L2CAP_PSM = 0x1023;
+    public static final int PBAP_L2CAP_PSM = 0x1025;
+    public static final int MNS_L2CAP_PSM = 0x1027;
+    public static final int MAP_L2CAP_PSM = 0x1029;
+
+    public static final int OPP_RFCOMM_CHANNEL = 12;
+    public static final int SAP_RFCOMM_CHANNEL = 16;
+    public static final int PBAP_RFCOMM_CHANNEL = 19;
+    public static final int MNS_RFCOMM_CHANNEL = 22;
+    /* SMS/MMS will use channel 26 & Email channels are set from 27 to 30*/
+    public static final int MAP_RFCOMM_CHANNEL = 26;
+
+    public static final int NEXT_RFCOMM_CHANNEL = 1;
+    public static final int NEXT_L2CAP_CHANNEL = 2;
+
     /* Variables to keep track of ongoing and queued search requests.
      * mTrackerLock must be held, when using/changing sSdpSearchTracker
      * and mSearchInProgress. */