DO NOT MERGE - Merge pie-platform-release (PPRL.190801.002) into master

Bug: 139369544
Change-Id: If9b192f2b6d33372c1a255bd9b67fec1ce4fd889
diff --git a/Android.mk b/Android.mk
index 5d3db9a..f4a0d5d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -25,11 +25,60 @@
         sap-api-java-static \
         services.net \
         libprotobuf-java-lite \
-        bluetooth-protos-lite
+        bluetooth-protos-lite \
 
-LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v4
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+        androidx.core_core \
+        androidx.legacy_legacy-support-v4 \
+        androidx.lifecycle_lifecycle-livedata \
+        androidx.room_room-runtime \
+        bt-androidx-room-runtime-nodeps \
+
+LOCAL_ANNOTATION_PROCESSORS := \
+        bt-androidx-annotation-nodeps \
+        bt-androidx-room-common-nodeps \
+        bt-androidx-room-compiler-nodeps \
+        bt-androidx-room-migration-nodeps \
+        bt-antlr4-nodeps \
+        bt-apache-commons-codec-nodeps \
+        bt-auto-common-nodeps \
+        bt-javapoet-nodeps \
+        bt-kotlin-metadata-nodeps \
+        bt-sqlite-jdbc-nodeps \
+        bt-jetbrain-nodeps \
+        guava-21.0 \
+        kotlin-stdlib \
+        gson-prebuilt-jar
+
+LOCAL_JAVACFLAGS := \
+        -Aroom.schemaLocation=$(LOCAL_PATH)/tests/unit/src/com/android/bluetooth/btservice/storage/schemas
+
+LOCAL_ANNOTATION_PROCESSOR_CLASSES := \
+        androidx.room.RoomProcessor
+
 LOCAL_REQUIRED_MODULES := libbluetooth
 LOCAL_PROGUARD_ENABLED := disabled
 include $(BUILD_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
+
+include $(CLEAR_VARS)
+
+COMMON_LIBS_PATH := ../../../../../prebuilts/tools/common/m2/repository
+ROOM_LIBS_PATH := ../../lib/room
+
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+        bt-androidx-annotation-nodeps:$(ROOM_LIBS_PATH)/annotation-1.0.0-beta01.jar \
+        bt-androidx-room-common-nodeps:$(ROOM_LIBS_PATH)/room-common-2.0.0-beta01.jar \
+        bt-androidx-room-compiler-nodeps:$(ROOM_LIBS_PATH)/room-compiler-2.0.0-beta01.jar \
+        bt-androidx-room-migration-nodeps:$(ROOM_LIBS_PATH)/room-migration-2.0.0-beta01.jar \
+        bt-androidx-room-runtime-nodeps:$(ROOM_LIBS_PATH)/room-runtime-2.0.0-alpha1.aar \
+        bt-antlr4-nodeps:$(COMMON_LIBS_PATH)/org/antlr/antlr4/4.5.3/antlr4-4.5.3.jar \
+        bt-apache-commons-codec-nodeps:$(COMMON_LIBS_PATH)/org/eclipse/tycho/tycho-bundles-external/0.18.1/eclipse/plugins/org.apache.commons.codec_1.4.0.v201209201156.jar \
+        bt-auto-common-nodeps:$(COMMON_LIBS_PATH)/com/google/auto/auto-common/0.9/auto-common-0.9.jar \
+        bt-javapoet-nodeps:$(COMMON_LIBS_PATH)/com/squareup/javapoet/1.8.0/javapoet-1.8.0.jar \
+        bt-kotlin-metadata-nodeps:$(COMMON_LIBS_PATH)/me/eugeniomarletti/kotlin-metadata/1.2.1/kotlin-metadata-1.2.1.jar \
+        bt-sqlite-jdbc-nodeps:$(COMMON_LIBS_PATH)/org/xerial/sqlite-jdbc/3.20.1/sqlite-jdbc-3.20.1.jar \
+        bt-jetbrain-nodeps:../../../../../prebuilts/tools/common/m2/repository/org/jetbrains/annotations/13.0/annotations-13.0.jar
+
+include $(BUILD_HOST_PREBUILT)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b24064f..5add14e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -58,7 +58,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" />
@@ -69,6 +68,8 @@
     <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
 
+    <uses-sdk android:minSdkVersion="14"/>
+
     <!-- For PBAP Owner Vcard Info -->
     <uses-permission android:name="android.permission.READ_PROFILE"/>
     <application
@@ -309,7 +310,7 @@
         </service>
         <service
             android:process="@string/process"
-            android:name=".a2dpsink.mbs.A2dpMediaBrowserService"
+            android:name=".avrcpcontroller.BluetoothMediaBrowserService"
             android:exported="true"
             android:enabled="@bool/profile_supported_a2dp_sink"
             android:label="@string/a2dp_sink_mbs_label">
@@ -351,14 +352,6 @@
         </service>
         <service
             android:process="@string/process"
-            android:name = ".hdp.HealthService"
-            android:enabled="@bool/profile_supported_hdp">
-            <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothHealth" />
-            </intent-filter>
-        </service>
-        <service
-            android:process="@string/process"
             android:name = ".pan.PanService"
             android:enabled="@bool/profile_supported_pan">
             <intent-filter>
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..2c1bdc4
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/system/bt:/OWNERS
diff --git a/jni/Android.bp b/jni/Android.bp
index c0fdd80..9005ae8 100644
--- a/jni/Android.bp
+++ b/jni/Android.bp
@@ -8,13 +8,11 @@
         "com_android_bluetooth_hfpclient.cpp",
         "com_android_bluetooth_a2dp.cpp",
         "com_android_bluetooth_a2dp_sink.cpp",
-        "com_android_bluetooth_avrcp.cpp",
         "com_android_bluetooth_avrcp_controller.cpp",
         "com_android_bluetooth_avrcp_target.cpp",
         "com_android_bluetooth_hid_host.cpp",
         "com_android_bluetooth_hid_device.cpp",
         "com_android_bluetooth_hearing_aid.cpp",
-        "com_android_bluetooth_hdp.cpp",
         "com_android_bluetooth_pan.cpp",
         "com_android_bluetooth_gatt.cpp",
         "com_android_bluetooth_sdp.cpp",
@@ -23,20 +21,20 @@
     ],
     header_libs: ["libbluetooth_headers"],
     include_dirs: [
-        "libnativehelper/include/nativehelper",
         "system/bt/types",
     ],
     shared_libs: [
         "libandroid_runtime",
+        "libbase",
         "libbinder",
         "libbluetooth-binder",
         "libchrome",
         "libnativehelper",
         "liblog",
+        "libutils",
     ],
     static_libs: [
         "libbluetooth-types",
-        "libutils",
         "libcutils",
     ],
     cflags: [
diff --git a/jni/bluetooth_socket_manager.cc b/jni/bluetooth_socket_manager.cc
index a63beed..ba45aa5 100644
--- a/jni/bluetooth_socket_manager.cc
+++ b/jni/bluetooth_socket_manager.cc
@@ -20,7 +20,6 @@
 #include <base/logging.h>
 #include <binder/IPCThreadState.h>
 
-using ::android::OK;
 using ::android::String8;
 using ::android::binder::Status;
 using ::android::os::ParcelFileDescriptor;
@@ -57,8 +56,8 @@
     return Status::ok();
   }
 
-  _aidl_return->reset(new ParcelFileDescriptor());
-  (*_aidl_return)->setFileDescriptor(socket_fd, true);
+  _aidl_return->reset(
+      new ParcelFileDescriptor(android::base::unique_fd(socket_fd)));
   return Status::ok();
 }
 
@@ -97,8 +96,8 @@
     return Status::ok();
   }
 
-  _aidl_return->reset(new ParcelFileDescriptor());
-  (*_aidl_return)->setFileDescriptor(socket_fd, true);
+  _aidl_return->reset(
+      new ParcelFileDescriptor(android::base::unique_fd(socket_fd)));
   return Status::ok();
 }
 
diff --git a/jni/com_android_bluetooth.h b/jni/com_android_bluetooth.h
index 9f136a0..c305c16 100644
--- a/jni/com_android_bluetooth.h
+++ b/jni/com_android_bluetooth.h
@@ -89,8 +89,6 @@
 
 int register_com_android_bluetooth_hid_device(JNIEnv* env);
 
-int register_com_android_bluetooth_hdp(JNIEnv* env);
-
 int register_com_android_bluetooth_pan(JNIEnv* env);
 
 int register_com_android_bluetooth_gatt (JNIEnv* env);
diff --git a/jni/com_android_bluetooth_a2dp.cpp b/jni/com_android_bluetooth_a2dp.cpp
index 126405a3..af125bd 100644
--- a/jni/com_android_bluetooth_a2dp.cpp
+++ b/jni/com_android_bluetooth_a2dp.cpp
@@ -390,6 +390,33 @@
   return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
+static jboolean setSilenceDeviceNative(JNIEnv* env, jobject object,
+                                       jbyteArray address, jboolean silence) {
+  ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface);
+  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+  if (!sBluetoothA2dpInterface) {
+    ALOGE("%s: Failed to get the Bluetooth A2DP Interface", __func__);
+    return JNI_FALSE;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
+
+  RawAddress bd_addr = RawAddress::kEmpty;
+  if (addr) {
+    bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
+  }
+  if (bd_addr == RawAddress::kEmpty) {
+    return JNI_FALSE;
+  }
+  bt_status_t status =
+      sBluetoothA2dpInterface->set_silence_device(bd_addr, silence);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("%s: Failed A2DP set_silence_device, status: %d", __func__, status);
+  }
+  env->ReleaseByteArrayElements(address, addr, 0);
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
 static jboolean setActiveDeviceNative(JNIEnv* env, jobject object,
                                       jbyteArray address) {
   ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface);
@@ -405,6 +432,9 @@
   if (addr) {
     bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
   }
+  if (bd_addr == RawAddress::kEmpty) {
+    return JNI_FALSE;
+  }
   bt_status_t status = sBluetoothA2dpInterface->set_active_device(bd_addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("%s: Failed A2DP set_active_device, status: %d", __func__, status);
@@ -450,6 +480,7 @@
     {"cleanupNative", "()V", (void*)cleanupNative},
     {"connectA2dpNative", "([B)Z", (void*)connectA2dpNative},
     {"disconnectA2dpNative", "([B)Z", (void*)disconnectA2dpNative},
+    {"setSilenceDeviceNative", "([BZ)Z", (void*)setSilenceDeviceNative},
     {"setActiveDeviceNative", "([B)Z", (void*)setActiveDeviceNative},
     {"setCodecConfigPreferenceNative",
      "([B[Landroid/bluetooth/BluetoothCodecConfig;)Z",
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
deleted file mode 100644
index 1eb3553..0000000
--- a/jni/com_android_bluetooth_avrcp.cpp
+++ /dev/null
@@ -1,1529 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "BluetoothAvrcpServiceJni"
-
-#define LOG_NDEBUG 0
-
-#include "android_runtime/AndroidRuntime.h"
-#include "com_android_bluetooth.h"
-#include "hardware/bt_rc.h"
-#include "utils/Log.h"
-
-#include <inttypes.h>
-#include <string.h>
-
-namespace android {
-static jmethodID method_getRcFeatures;
-static jmethodID method_getPlayStatus;
-static jmethodID method_getElementAttr;
-static jmethodID method_registerNotification;
-static jmethodID method_volumeChangeCallback;
-static jmethodID method_handlePassthroughCmd;
-static jmethodID method_getFolderItemsCallback;
-static jmethodID method_setAddressedPlayerCallback;
-
-static jmethodID method_setBrowsedPlayerCallback;
-static jmethodID method_changePathCallback;
-static jmethodID method_searchCallback;
-static jmethodID method_playItemCallback;
-static jmethodID method_getItemAttrCallback;
-static jmethodID method_addToPlayListCallback;
-static jmethodID method_getTotalNumOfItemsCallback;
-
-static const btrc_interface_t* sBluetoothAvrcpInterface = NULL;
-static jobject mCallbacksObj = NULL;
-
-/* Function declarations */
-static bool copy_item_attributes(JNIEnv* env, jobject object,
-                                 btrc_folder_items_t* pitem,
-                                 jint* p_attributesIds,
-                                 jobjectArray attributesArray, int item_idx,
-                                 int attribCopiedIndex);
-
-static bool copy_jstring(uint8_t* str, int maxBytes, jstring jstr, JNIEnv* env);
-
-static void cleanup_items(btrc_folder_items_t* p_items, int numItems);
-
-static void btavrcp_remote_features_callback(const RawAddress& bd_addr,
-                                             btrc_remote_features_t features) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Unable to allocate byte array for bd_addr");
-    return;
-  }
-
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getRcFeatures, addr.get(),
-                               (jint)features);
-}
-
-/** Callback for play status request */
-static void btavrcp_get_play_status_callback(const RawAddress& bd_addr) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for get_play_status command");
-    return;
-  }
-
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getPlayStatus, addr.get());
-}
-
-static void btavrcp_get_element_attr_callback(uint8_t num_attr,
-                                              btrc_media_attr_t* p_attrs,
-                                              const RawAddress& bd_addr) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for get_element_attr command");
-    return;
-  }
-
-  ScopedLocalRef<jintArray> attrs(
-      sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(num_attr));
-  if (!attrs.get()) {
-    ALOGE("Fail to new jintArray for attrs");
-    return;
-  }
-
-  sCallbackEnv->SetIntArrayRegion(attrs.get(), 0, num_attr, (jint*)p_attrs);
-
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getElementAttr, addr.get(),
-                               (jbyte)num_attr, attrs.get());
-}
-
-static void btavrcp_register_notification_callback(btrc_event_id_t event_id,
-                                                   uint32_t param,
-                                                   const RawAddress& bd_addr) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for register_notification command");
-    return;
-  }
-
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_registerNotification,
-                               addr.get(), (jint)event_id, (jint)param);
-}
-
-static void btavrcp_volume_change_callback(uint8_t volume, uint8_t ctype,
-                                           const RawAddress& bd_addr) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for volume_change command");
-    return;
-  }
-
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_volumeChangeCallback,
-                               addr.get(), (jint)volume, (jint)ctype);
-}
-
-static void btavrcp_passthrough_command_callback(int id, int pressed,
-                                                 const RawAddress& bd_addr) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for passthrough_command command");
-    return;
-  }
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handlePassthroughCmd,
-                               addr.get(), (jint)id, (jint)pressed);
-}
-
-static void btavrcp_set_addressed_player_callback(uint16_t player_id,
-                                                  const RawAddress& bd_addr) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for set_addressed_player command");
-    return;
-  }
-
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_setAddressedPlayerCallback,
-                               addr.get(), (jint)player_id);
-}
-
-static void btavrcp_set_browsed_player_callback(uint16_t player_id,
-                                                const RawAddress& bd_addr) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for set_browsed_player command");
-    return;
-  }
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_setBrowsedPlayerCallback,
-                               addr.get(), (jint)player_id);
-}
-
-static void btavrcp_get_folder_items_callback(
-    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__);
-  if (!sCallbackEnv.valid()) return;
-
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for get_folder_items command");
-    return;
-  }
-
-  uint32_t* puiAttr = (uint32_t*)p_attr_ids;
-  ScopedLocalRef<jintArray> attr_ids(sCallbackEnv.get(), NULL);
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-
-  /* check number of attributes requested by remote device */
-  if ((num_attr != BTRC_NUM_ATTR_ALL) && (num_attr != BTRC_NUM_ATTR_NONE)) {
-    /* allocate memory for attr_ids only if some attributes passed from below
-     * layer */
-    attr_ids.reset((jintArray)sCallbackEnv->NewIntArray(num_attr));
-    if (!attr_ids.get()) {
-      ALOGE("Fail to allocate new jintArray for attrs");
-      return;
-    }
-    sCallbackEnv->SetIntArrayRegion(attr_ids.get(), 0, num_attr,
-                                    (jint*)puiAttr);
-  }
-
-  sCallbackEnv->CallVoidMethod(
-      mCallbacksObj, method_getFolderItemsCallback, addr.get(), (jbyte)scope,
-      (jlong)start_item, (jlong)end_item, (jbyte)num_attr, attr_ids.get());
-}
-
-static void btavrcp_change_path_callback(uint8_t direction, uint8_t* folder_uid,
-                                         const RawAddress& bd_addr) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> attrs(sCallbackEnv.get(),
-                                   sCallbackEnv->NewByteArray(BTRC_UID_SIZE));
-  if (!attrs.get()) {
-    ALOGE("Fail to new jintArray for attrs");
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for change_path command");
-    return;
-  }
-
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-  sCallbackEnv->SetByteArrayRegion(
-      attrs.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)folder_uid);
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_changePathCallback,
-                               addr.get(), (jbyte)direction, attrs.get());
-}
-
-static void btavrcp_get_item_attr_callback(uint8_t scope, uint8_t* uid,
-                                           uint16_t uid_counter,
-                                           uint8_t num_attr,
-                                           btrc_media_attr_t* p_attrs,
-                                           const RawAddress& bd_addr) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> attr_uid(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(BTRC_UID_SIZE));
-  if (!attr_uid.get()) {
-    ALOGE("Fail to new jintArray for attr_uid");
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for get_item_attr command");
-    return;
-  }
-
-  ScopedLocalRef<jintArray> attrs(
-      sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(num_attr));
-  if (!attrs.get()) {
-    ALOGE("Fail to new jintArray for attrs");
-    return;
-  }
-
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-  sCallbackEnv->SetIntArrayRegion(attrs.get(), 0, num_attr, (jint*)p_attrs);
-  sCallbackEnv->SetByteArrayRegion(
-      attr_uid.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)uid);
-
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getItemAttrCallback,
-                               addr.get(), (jbyte)scope, attr_uid.get(),
-                               (jint)uid_counter, (jbyte)num_attr, attrs.get());
-}
-
-static void btavrcp_play_item_callback(uint8_t scope, uint16_t uid_counter,
-                                       uint8_t* uid,
-                                       const RawAddress& bd_addr) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> attrs(sCallbackEnv.get(),
-                                   sCallbackEnv->NewByteArray(BTRC_UID_SIZE));
-  if (!attrs.get()) {
-    ALOGE("%s: Fail to new jByteArray attrs for play_item command", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for play_item command");
-    return;
-  }
-
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-  sCallbackEnv->SetByteArrayRegion(
-      attrs.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)uid);
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_playItemCallback,
-                               addr.get(), (jbyte)scope, (jint)uid_counter,
-                               attrs.get());
-}
-
-static void btavrcp_get_total_num_items_callback(uint8_t scope,
-                                                 const RawAddress& bd_addr) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for get_total_num_items command");
-    return;
-  }
-
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getTotalNumOfItemsCallback,
-                               addr.get(), (jbyte)scope);
-}
-
-static void btavrcp_search_callback(uint16_t charset_id, uint16_t str_len,
-                                    uint8_t* p_str, const RawAddress& bd_addr) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> attrs(sCallbackEnv.get(),
-                                   sCallbackEnv->NewByteArray(str_len));
-  if (!attrs.get()) {
-    ALOGE("Fail to new jintArray for attrs");
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for search command");
-    return;
-  }
-
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-  sCallbackEnv->SetByteArrayRegion(attrs.get(), 0, str_len * sizeof(uint8_t),
-                                   (jbyte*)p_str);
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_searchCallback, addr.get(),
-                               (jint)charset_id, attrs.get());
-}
-
-static void btavrcp_add_to_play_list_callback(uint8_t scope, uint8_t* uid,
-                                              uint16_t uid_counter,
-                                              const RawAddress& bd_addr) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-  if (!mCallbacksObj) {
-    ALOGE("%s: mCallbacksObj is null", __func__);
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for add_to_play_list command");
-    return;
-  }
-
-  ScopedLocalRef<jbyteArray> attrs(sCallbackEnv.get(),
-                                   sCallbackEnv->NewByteArray(BTRC_UID_SIZE));
-  if (!attrs.get()) {
-    ALOGE("Fail to new jByteArray for attrs");
-    return;
-  }
-
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr.address);
-  sCallbackEnv->SetByteArrayRegion(
-      attrs.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)uid);
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_addToPlayListCallback,
-                               addr.get(), (jbyte)scope, attrs.get(),
-                               (jint)uid_counter);
-}
-
-static btrc_callbacks_t sBluetoothAvrcpCallbacks = {
-    sizeof(sBluetoothAvrcpCallbacks),
-    btavrcp_remote_features_callback,
-    btavrcp_get_play_status_callback,
-    NULL,
-    NULL,
-    NULL,
-    NULL,
-    NULL,
-    NULL,
-    btavrcp_get_element_attr_callback,
-    btavrcp_register_notification_callback,
-    btavrcp_volume_change_callback,
-    btavrcp_passthrough_command_callback,
-    btavrcp_set_addressed_player_callback,
-    btavrcp_set_browsed_player_callback,
-    btavrcp_get_folder_items_callback,
-    btavrcp_change_path_callback,
-    btavrcp_get_item_attr_callback,
-    btavrcp_play_item_callback,
-    btavrcp_get_total_num_items_callback,
-    btavrcp_search_callback,
-    btavrcp_add_to_play_list_callback,
-};
-
-static void classInitNative(JNIEnv* env, jclass clazz) {
-  method_getRcFeatures =
-      env->GetMethodID(clazz, "getRcFeaturesRequestFromNative", "([BI)V");
-  method_getPlayStatus =
-      env->GetMethodID(clazz, "getPlayStatusRequestFromNative", "([B)V");
-
-  method_getElementAttr =
-      env->GetMethodID(clazz, "getElementAttrRequestFromNative", "([BB[I)V");
-
-  method_registerNotification = env->GetMethodID(
-      clazz, "registerNotificationRequestFromNative", "([BII)V");
-
-  method_volumeChangeCallback =
-      env->GetMethodID(clazz, "volumeChangeRequestFromNative", "([BII)V");
-
-  method_handlePassthroughCmd = env->GetMethodID(
-      clazz, "handlePassthroughCmdRequestFromNative", "([BII)V");
-
-  method_setAddressedPlayerCallback =
-      env->GetMethodID(clazz, "setAddressedPlayerRequestFromNative", "([BI)V");
-
-  method_setBrowsedPlayerCallback =
-      env->GetMethodID(clazz, "setBrowsedPlayerRequestFromNative", "([BI)V");
-
-  method_getFolderItemsCallback =
-      env->GetMethodID(clazz, "getFolderItemsRequestFromNative", "([BBJJB[I)V");
-
-  method_changePathCallback =
-      env->GetMethodID(clazz, "changePathRequestFromNative", "([BB[B)V");
-
-  method_getItemAttrCallback =
-      env->GetMethodID(clazz, "getItemAttrRequestFromNative", "([BB[BIB[I)V");
-
-  method_playItemCallback =
-      env->GetMethodID(clazz, "playItemRequestFromNative", "([BBI[B)V");
-
-  method_getTotalNumOfItemsCallback =
-      env->GetMethodID(clazz, "getTotalNumOfItemsRequestFromNative", "([BB)V");
-
-  method_searchCallback =
-      env->GetMethodID(clazz, "searchRequestFromNative", "([BI[B)V");
-
-  method_addToPlayListCallback =
-      env->GetMethodID(clazz, "addToPlayListRequestFromNative", "([BB[BI)V");
-
-  ALOGI("%s: succeeds", __func__);
-}
-
-static void initNative(JNIEnv* env, jobject object) {
-  const bt_interface_t* btInf = getBluetoothInterface();
-  if (btInf == NULL) {
-    ALOGE("Bluetooth module is not loaded");
-    return;
-  }
-
-  if (sBluetoothAvrcpInterface != NULL) {
-    ALOGW("Cleaning up Avrcp Interface before initializing...");
-    sBluetoothAvrcpInterface->cleanup();
-    sBluetoothAvrcpInterface = NULL;
-  }
-
-  if (mCallbacksObj != NULL) {
-    ALOGW("Cleaning up Avrcp callback object");
-    env->DeleteGlobalRef(mCallbacksObj);
-    mCallbacksObj = NULL;
-  }
-
-  sBluetoothAvrcpInterface =
-      (btrc_interface_t*)btInf->get_profile_interface(BT_PROFILE_AV_RC_ID);
-  if (sBluetoothAvrcpInterface == NULL) {
-    ALOGE("Failed to get Bluetooth Avrcp Interface");
-    return;
-  }
-
-  bt_status_t status =
-      sBluetoothAvrcpInterface->init(&sBluetoothAvrcpCallbacks);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed to initialize Bluetooth Avrcp, status: %d", status);
-    sBluetoothAvrcpInterface = NULL;
-    return;
-  }
-
-  mCallbacksObj = env->NewGlobalRef(object);
-}
-
-static void cleanupNative(JNIEnv* env, jobject object) {
-  const bt_interface_t* btInf = getBluetoothInterface();
-  if (btInf == NULL) {
-    ALOGE("Bluetooth module is not loaded");
-    return;
-  }
-
-  if (sBluetoothAvrcpInterface != NULL) {
-    sBluetoothAvrcpInterface->cleanup();
-    sBluetoothAvrcpInterface = NULL;
-  }
-
-  if (mCallbacksObj != NULL) {
-    env->DeleteGlobalRef(mCallbacksObj);
-    mCallbacksObj = NULL;
-  }
-}
-
-static jboolean getPlayStatusRspNative(JNIEnv* env, jobject object,
-                                       jbyteArray address, jint playStatus,
-                                       jint songLen, jint songPos) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-  RawAddress rawAddress;
-  rawAddress.FromOctets((uint8_t*)addr);
-
-  bt_status_t status = sBluetoothAvrcpInterface->get_play_status_rsp(
-      rawAddress, (btrc_play_status_t)playStatus, songLen, songPos);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed get_play_status_rsp, status: %d", status);
-  }
-  env->ReleaseByteArrayElements(address, addr, 0);
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean getElementAttrRspNative(JNIEnv* env, jobject object,
-                                        jbyteArray address, jbyte numAttr,
-                                        jintArray attrIds,
-                                        jobjectArray textArray) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  if (numAttr > BTRC_MAX_ELEM_ATTR_SIZE) {
-    ALOGE("get_element_attr_rsp: number of attributes exceed maximum");
-    return JNI_FALSE;
-  }
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  btrc_element_attr_val_t* pAttrs = new btrc_element_attr_val_t[numAttr];
-  if (!pAttrs) {
-    ALOGE("get_element_attr_rsp: not have enough memeory");
-    env->ReleaseByteArrayElements(address, addr, 0);
-    return JNI_FALSE;
-  }
-
-  jint* attr = env->GetIntArrayElements(attrIds, NULL);
-  if (!attr) {
-    delete[] pAttrs;
-    jniThrowIOException(env, EINVAL);
-    env->ReleaseByteArrayElements(address, addr, 0);
-    return JNI_FALSE;
-  }
-
-  int attr_cnt;
-  for (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)) {
-      break;
-    }
-  }
-
-  if (attr_cnt < numAttr) {
-    delete[] pAttrs;
-    env->ReleaseIntArrayElements(attrIds, attr, 0);
-    ALOGE("%s: Failed to copy attributes", __func__);
-    return JNI_FALSE;
-  }
-
-  RawAddress rawAddress;
-  rawAddress.FromOctets((uint8_t*)addr);
-  bt_status_t status = sBluetoothAvrcpInterface->get_element_attr_rsp(
-      rawAddress, numAttr, pAttrs);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed get_element_attr_rsp, status: %d", status);
-  }
-
-  delete[] pAttrs;
-  env->ReleaseIntArrayElements(attrIds, attr, 0);
-  env->ReleaseByteArrayElements(address, addr, 0);
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean getItemAttrRspNative(JNIEnv* env, jobject object,
-                                     jbyteArray address, jint rspStatus,
-                                     jbyte numAttr, jintArray attrIds,
-                                     jobjectArray textArray) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  if (numAttr > BTRC_MAX_ELEM_ATTR_SIZE) {
-    ALOGE("get_element_attr_rsp: number of attributes exceed maximum");
-    return JNI_FALSE;
-  }
-
-  btrc_element_attr_val_t* pAttrs = new btrc_element_attr_val_t[numAttr];
-  if (!pAttrs) {
-    ALOGE("%s: not have enough memory", __func__);
-    env->ReleaseByteArrayElements(address, addr, 0);
-    return JNI_FALSE;
-  }
-
-  jint* attr = NULL;
-  if (attrIds != NULL) {
-    attr = env->GetIntArrayElements(attrIds, NULL);
-    if (!attr) {
-      delete[] pAttrs;
-      jniThrowIOException(env, EINVAL);
-      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));
-
-    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;
-  rawAddress.FromOctets((uint8_t*)addr);
-
-  bt_status_t status = sBluetoothAvrcpInterface->get_item_attr_rsp(
-      rawAddress, (btrc_status_t)rspStatus, numAttr, pAttrs);
-  if (status != BT_STATUS_SUCCESS)
-    ALOGE("Failed get_item_attr_rsp, status: %d", status);
-
-  if (pAttrs) delete[] pAttrs;
-  if (attr) env->ReleaseIntArrayElements(attrIds, attr, 0);
-  env->ReleaseByteArrayElements(address, addr, 0);
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspPlayStatusNative(JNIEnv* env,
-                                                        jobject object,
-                                                        jint type,
-                                                        jint playStatus) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  btrc_register_notification_t param;
-  param.play_status = (btrc_play_status_t)playStatus;
-
-  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
-      BTRC_EVT_PLAY_STATUS_CHANGED, (btrc_notification_type_t)type, &param);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed register_notification_rsp play status, status: %d", status);
-  }
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspTrackChangeNative(JNIEnv* env,
-                                                         jobject object,
-                                                         jint type,
-                                                         jbyteArray track) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  jbyte* trk = env->GetByteArrayElements(track, NULL);
-  if (!trk) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  btrc_register_notification_t param;
-  uint64_t uid = 0;
-  for (int uid_idx = 0; uid_idx < BTRC_UID_SIZE; ++uid_idx) {
-    param.track[uid_idx] = trk[uid_idx];
-    uid = uid + (trk[uid_idx] << (BTRC_UID_SIZE - 1 - uid_idx));
-  }
-
-  ALOGV("%s: Sending track change notification: %d -> %" PRIu64, __func__, type,
-        uid);
-
-  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
-      BTRC_EVT_TRACK_CHANGE, (btrc_notification_type_t)type, &param);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed register_notification_rsp track change, status: %d", status);
-  }
-
-  env->ReleaseByteArrayElements(track, trk, 0);
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspPlayPosNative(JNIEnv* env,
-                                                     jobject object, jint type,
-                                                     jint playPos) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  btrc_register_notification_t param;
-  param.song_pos = (uint32_t)playPos;
-
-  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
-      BTRC_EVT_PLAY_POS_CHANGED, (btrc_notification_type_t)type, &param);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed register_notification_rsp play position, status: %d", status);
-  }
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspNowPlayingChangedNative(JNIEnv* env,
-                                                               jobject object,
-                                                               jint type) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  btrc_register_notification_t param;
-  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
-      BTRC_EVT_NOW_PLAYING_CONTENT_CHANGED, (btrc_notification_type_t)type,
-      &param);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed register_notification_rsp, nowPlaying Content status: %d",
-          status);
-  }
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspUIDsChangedNative(JNIEnv* env,
-                                                         jobject object,
-                                                         jint type,
-                                                         jint uidCounter) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  btrc_register_notification_t param;
-  param.uids_changed.uid_counter = (uint16_t)uidCounter;
-
-  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
-      BTRC_EVT_UIDS_CHANGED, (btrc_notification_type_t)type, &param);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed register_notification_rsp, uids changed status: %d", status);
-  }
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspAddrPlayerChangedNative(
-    JNIEnv* env, jobject object, jint type, jint playerId, jint uidCounter) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  btrc_register_notification_t param;
-  param.addr_player_changed.player_id = (uint16_t)playerId;
-  param.addr_player_changed.uid_counter = (uint16_t)uidCounter;
-
-  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
-      BTRC_EVT_ADDR_PLAYER_CHANGE, (btrc_notification_type_t)type, &param);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed register_notification_rsp address player changed status: %d",
-          status);
-  }
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspAvalPlayerChangedNative(JNIEnv* env,
-                                                               jobject object,
-                                                               jint type) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  btrc_register_notification_t param;
-  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
-      BTRC_EVT_AVAL_PLAYER_CHANGE, (btrc_notification_type_t)type, &param);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE(
-        "Failed register_notification_rsp available player changed status, "
-        "status: %d",
-        status);
-  }
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean setVolumeNative(JNIEnv* env, jobject object, jint volume) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  bt_status_t status = sBluetoothAvrcpInterface->set_volume((uint8_t)volume);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed set_volume, status: %d", status);
-  }
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-/* native response for scope as Media player */
-static jboolean mediaPlayerListRspNative(
-    JNIEnv* env, jobject object, jbyteArray address, jint rspStatus,
-    jint uidCounter, jbyte itemType, jint numItems, jintArray playerIds,
-    jbyteArray playerTypes, jintArray playerSubtypes,
-    jbyteArray playStatusValues, jshortArray featureBitmask,
-    jobjectArray textArray) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  jbyte *p_playerTypes = NULL, *p_PlayStatusValues = NULL;
-  jshort* p_FeatBitMaskValues = NULL;
-  jint *p_playerIds = NULL, *p_playerSubTypes = NULL;
-  btrc_folder_items_t* p_items = NULL;
-  if (rspStatus == BTRC_STS_NO_ERROR) {
-    /* allocate memory */
-    p_playerIds = env->GetIntArrayElements(playerIds, NULL);
-    p_playerTypes = env->GetByteArrayElements(playerTypes, NULL);
-    p_playerSubTypes = env->GetIntArrayElements(playerSubtypes, NULL);
-    p_PlayStatusValues = env->GetByteArrayElements(playStatusValues, NULL);
-    p_FeatBitMaskValues = env->GetShortArrayElements(featureBitmask, NULL);
-    p_items = new btrc_folder_items_t[numItems];
-    /* deallocate memory and return if allocation failed */
-    if (!p_playerIds || !p_playerTypes || !p_playerSubTypes ||
-        !p_PlayStatusValues || !p_FeatBitMaskValues || !p_items) {
-      if (p_playerIds) env->ReleaseIntArrayElements(playerIds, p_playerIds, 0);
-      if (p_playerTypes)
-        env->ReleaseByteArrayElements(playerTypes, p_playerTypes, 0);
-      if (p_playerSubTypes)
-        env->ReleaseIntArrayElements(playerSubtypes, p_playerSubTypes, 0);
-      if (p_PlayStatusValues)
-        env->ReleaseByteArrayElements(playStatusValues, p_PlayStatusValues, 0);
-      if (p_FeatBitMaskValues)
-        env->ReleaseShortArrayElements(featureBitmask, p_FeatBitMaskValues, 0);
-      if (p_items) delete[] p_items;
-
-      jniThrowIOException(env, EINVAL);
-      ALOGE("%s: not have enough memory", __func__);
-      return JNI_FALSE;
-    }
-
-    p_items->item_type = (uint8_t)itemType;
-
-    /* copy list of media players along with other parameters */
-    int itemIdx;
-    for (itemIdx = 0; itemIdx < numItems; ++itemIdx) {
-      p_items[itemIdx].player.player_id = p_playerIds[itemIdx];
-      p_items[itemIdx].player.major_type = p_playerTypes[itemIdx];
-      p_items[itemIdx].player.sub_type = p_playerSubTypes[itemIdx];
-      p_items[itemIdx].player.play_status = p_PlayStatusValues[itemIdx];
-      p_items[itemIdx].player.charset_id = BTRC_CHARSET_ID_UTF8;
-
-      ScopedLocalRef<jstring> text(
-          env, (jstring)env->GetObjectArrayElement(textArray, itemIdx));
-      /* copy player name */
-      if (!copy_jstring(p_items[itemIdx].player.name, BTRC_MAX_ATTR_STR_LEN,
-                        text.get(), env))
-        break;
-
-      /* Feature bit mask is 128-bit value each */
-      for (int InnCnt = 0; InnCnt < 16; InnCnt++) {
-        p_items[itemIdx].player.features[InnCnt] =
-            (uint8_t)p_FeatBitMaskValues[(itemIdx * 16) + InnCnt];
-      }
-    }
-
-    /* failed to copy list of media players */
-    if (itemIdx < numItems) {
-      rspStatus = BTRC_STS_INTERNAL_ERR;
-      ALOGE("%s: Failed to copy Media player attributes", __func__);
-    }
-  }
-
-  RawAddress* btAddr = (RawAddress*)addr;
-  bt_status_t status = sBluetoothAvrcpInterface->get_folder_items_list_rsp(
-      *btAddr, (btrc_status_t)rspStatus, uidCounter, numItems, p_items);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed get_folder_items_list_rsp, status: %d", status);
-  }
-
-  /* release allocated memory */
-  if (p_items) delete[] p_items;
-  if (p_playerTypes)
-    env->ReleaseByteArrayElements(playerTypes, p_playerTypes, 0);
-  if (p_playerSubTypes)
-    env->ReleaseIntArrayElements(playerSubtypes, p_playerSubTypes, 0);
-  if (p_PlayStatusValues)
-    env->ReleaseByteArrayElements(playStatusValues, p_PlayStatusValues, 0);
-  if (p_FeatBitMaskValues) {
-    env->ReleaseShortArrayElements(featureBitmask, p_FeatBitMaskValues, 0);
-  }
-  env->ReleaseByteArrayElements(address, addr, 0);
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean getFolderItemsRspNative(
-    JNIEnv* env, jobject object, jbyteArray address, jint rspStatus,
-    jshort uidCounter, jbyte scope, jint numItems, jbyteArray folderType,
-    jbyteArray playable, jbyteArray itemType, jbyteArray itemUidArray,
-    jobjectArray displayNameArray, jintArray numAttrs, jintArray attributesIds,
-    jobjectArray attributesArray) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  jbyte *p_playable = NULL, *p_item_uid = NULL;
-  jbyte* p_item_types = NULL; /* Folder or Media Item */
-  jint* p_attributesIds = NULL;
-  jbyte* p_folder_types =
-      NULL; /* Folder properties like Album/Genre/Artists etc */
-  jint* p_num_attrs = NULL;
-  btrc_folder_items_t* p_items = NULL;
-  /* none of the parameters should be null when no error */
-  if (rspStatus == BTRC_STS_NO_ERROR) {
-    /* allocate memory to each rsp item */
-    if (folderType != NULL)
-      p_folder_types = env->GetByteArrayElements(folderType, NULL);
-    if (playable != NULL)
-      p_playable = env->GetByteArrayElements(playable, NULL);
-    if (itemType != NULL)
-      p_item_types = env->GetByteArrayElements(itemType, NULL);
-    if (NULL != numAttrs)
-      p_num_attrs = env->GetIntArrayElements(numAttrs, NULL);
-    if (NULL != attributesIds)
-      p_attributesIds = env->GetIntArrayElements(attributesIds, NULL);
-    if (itemUidArray != NULL)
-      p_item_uid = (jbyte*)env->GetByteArrayElements(itemUidArray, NULL);
-
-    p_items = new btrc_folder_items_t[numItems];
-
-    /* if memory alloc failed, release memory */
-    if (p_items && p_folder_types && p_playable && p_item_types && p_item_uid &&
-        /* attributes can be null if remote requests 0 attributes */
-        ((numAttrs != NULL && p_num_attrs) || (!numAttrs && !p_num_attrs)) &&
-        ((attributesIds != NULL && p_attributesIds) ||
-         (!attributesIds && !p_attributesIds))) {
-      memset(p_items, 0, sizeof(btrc_folder_items_t) * numItems);
-      if (scope == BTRC_SCOPE_FILE_SYSTEM || scope == BTRC_SCOPE_SEARCH ||
-          scope == BTRC_SCOPE_NOW_PLAYING) {
-        int attribCopiedIndex = 0;
-        for (int item_idx = 0; item_idx < numItems; item_idx++) {
-          if (BTRC_ITEM_FOLDER == p_item_types[item_idx]) {
-            btrc_folder_items_t* pitem = &p_items[item_idx];
-
-            memcpy(pitem->folder.uid, p_item_uid + item_idx * BTRC_UID_SIZE,
-                   BTRC_UID_SIZE);
-            pitem->item_type = (uint8_t)BTRC_ITEM_FOLDER;
-            pitem->folder.charset_id = BTRC_CHARSET_ID_UTF8;
-            pitem->folder.type = p_folder_types[item_idx];
-            pitem->folder.playable = p_playable[item_idx];
-
-            ScopedLocalRef<jstring> text(
-                env, (jstring)env->GetObjectArrayElement(displayNameArray,
-                                                         item_idx));
-            if (!copy_jstring(pitem->folder.name, BTRC_MAX_ATTR_STR_LEN,
-                              text.get(), env)) {
-              rspStatus = BTRC_STS_INTERNAL_ERR;
-              ALOGE("%s: failed to copy display name of folder item", __func__);
-              break;
-            }
-          } else if (BTRC_ITEM_MEDIA == p_item_types[item_idx]) {
-            btrc_folder_items_t* pitem = &p_items[item_idx];
-            memcpy(pitem->media.uid, p_item_uid + item_idx * BTRC_UID_SIZE,
-                   BTRC_UID_SIZE);
-
-            pitem->item_type = (uint8_t)BTRC_ITEM_MEDIA;
-            pitem->media.charset_id = BTRC_CHARSET_ID_UTF8;
-            pitem->media.type = BTRC_MEDIA_TYPE_AUDIO;
-            pitem->media.num_attrs =
-                (p_num_attrs != NULL) ? p_num_attrs[item_idx] : 0;
-
-            ScopedLocalRef<jstring> text(
-                env, (jstring)env->GetObjectArrayElement(displayNameArray,
-                                                         item_idx));
-            if (!copy_jstring(pitem->media.name, BTRC_MAX_ATTR_STR_LEN,
-                              text.get(), env)) {
-              rspStatus = BTRC_STS_INTERNAL_ERR;
-              ALOGE("%s: failed to copy display name of media item", __func__);
-              break;
-            }
-
-            /* copy item attributes */
-            if (!copy_item_attributes(env, object, pitem, p_attributesIds,
-                                      attributesArray, item_idx,
-                                      attribCopiedIndex)) {
-              ALOGE("%s: error in copying attributes of item = %s", __func__,
-                    pitem->media.name);
-              rspStatus = BTRC_STS_INTERNAL_ERR;
-              break;
-            }
-            attribCopiedIndex += pitem->media.num_attrs;
-          }
-        }
-      }
-    } else {
-      rspStatus = BTRC_STS_INTERNAL_ERR;
-      ALOGE("%s: unable to allocate memory", __func__);
-    }
-  }
-
-  RawAddress* btAddr = (RawAddress*)addr;
-  bt_status_t status = sBluetoothAvrcpInterface->get_folder_items_list_rsp(
-      *btAddr, (btrc_status_t)rspStatus, uidCounter, numItems, p_items);
-  if (status != BT_STATUS_SUCCESS)
-    ALOGE("Failed get_folder_items_list_rsp, status: %d", status);
-
-  /* Release allocated memory for all attributes in each media item */
-  if (p_items) cleanup_items(p_items, numItems);
-
-  /* Release allocated memory  */
-  if (p_folder_types)
-    env->ReleaseByteArrayElements(folderType, p_folder_types, 0);
-  if (p_playable) env->ReleaseByteArrayElements(playable, p_playable, 0);
-  if (p_item_types) env->ReleaseByteArrayElements(itemType, p_item_types, 0);
-  if (p_num_attrs) env->ReleaseIntArrayElements(numAttrs, p_num_attrs, 0);
-  if (p_attributesIds)
-    env->ReleaseIntArrayElements(attributesIds, p_attributesIds, 0);
-  if (p_item_uid) env->ReleaseByteArrayElements(itemUidArray, p_item_uid, 0);
-  if (p_items) delete[] p_items;
-  env->ReleaseByteArrayElements(address, addr, 0);
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean setAddressedPlayerRspNative(JNIEnv* env, jobject object,
-                                            jbyteArray address,
-                                            jint rspStatus) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  RawAddress* btAddr = (RawAddress*)addr;
-  bt_status_t status = sBluetoothAvrcpInterface->set_addressed_player_rsp(
-      *btAddr, (btrc_status_t)rspStatus);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed set_addressed_player_rsp, status: %d", status);
-  }
-  env->ReleaseByteArrayElements(address, addr, 0);
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean setBrowsedPlayerRspNative(JNIEnv* env, jobject object,
-                                          jbyteArray address, jint rspStatus,
-                                          jbyte depth, jint numItems,
-                                          jobjectArray textArray) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  btrc_br_folder_name_t* p_folders = NULL;
-  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__);
-        return JNI_FALSE;
-      }
-
-      p_folders[folder_idx].str_len =
-          strlen((char*)p_folders[folder_idx].p_str);
-    }
-  }
-
-  uint8_t folder_depth =
-      depth; /* folder_depth is 0 if current folder is root */
-  uint16_t charset_id = BTRC_CHARSET_ID_UTF8;
-  RawAddress* btAddr = (RawAddress*)addr;
-  bt_status_t status = sBluetoothAvrcpInterface->set_browsed_player_rsp(
-      *btAddr, (btrc_status_t)rspStatus, numItems, charset_id, folder_depth,
-      p_folders);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("%s: Failed set_browsed_player_rsp, status: %d", __func__, status);
-  }
-
-  if (depth > 0) {
-    delete[] p_folders;
-  }
-
-  env->ReleaseByteArrayElements(address, addr, 0);
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean changePathRspNative(JNIEnv* env, jobject object,
-                                    jbyteArray address, jint rspStatus,
-                                    jint numItems) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  uint32_t nItems = (uint32_t)numItems;
-  RawAddress* btAddr = (RawAddress*)addr;
-  bt_status_t status = sBluetoothAvrcpInterface->change_path_rsp(
-      *btAddr, (btrc_status_t)rspStatus, (uint32_t)nItems);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed change_path_rsp, status: %d", status);
-  }
-  env->ReleaseByteArrayElements(address, addr, 0);
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean searchRspNative(JNIEnv* env, jobject object, jbyteArray address,
-                                jint rspStatus, jint uidCounter,
-                                jint numItems) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  uint32_t nItems = (uint32_t)numItems;
-  RawAddress* btAddr = (RawAddress*)addr;
-  bt_status_t status = sBluetoothAvrcpInterface->search_rsp(
-      *btAddr, (btrc_status_t)rspStatus, (uint32_t)uidCounter,
-      (uint32_t)nItems);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed search_rsp, status: %d", status);
-  }
-
-  env->ReleaseByteArrayElements(address, addr, 0);
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean playItemRspNative(JNIEnv* env, jobject object,
-                                  jbyteArray address, jint rspStatus) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  RawAddress* btAddr = (RawAddress*)addr;
-  bt_status_t status = sBluetoothAvrcpInterface->play_item_rsp(
-      *btAddr, (btrc_status_t)rspStatus);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed play_item_rsp, status: %d", status);
-  }
-  env->ReleaseByteArrayElements(address, addr, 0);
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean getTotalNumOfItemsRspNative(JNIEnv* env, jobject object,
-                                            jbyteArray address, jint rspStatus,
-                                            jint uidCounter, jint numItems) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  uint32_t nItems = (uint32_t)numItems;
-  RawAddress* btAddr = (RawAddress*)addr;
-  bt_status_t status = sBluetoothAvrcpInterface->get_total_num_of_items_rsp(
-      *btAddr, (btrc_status_t)rspStatus, (uint32_t)uidCounter,
-      (uint32_t)nItems);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed get_total_num_of_items_rsp, status: %d", status);
-  }
-  env->ReleaseByteArrayElements(address, addr, 0);
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean addToNowPlayingRspNative(JNIEnv* env, jobject object,
-                                         jbyteArray address, jint rspStatus) {
-  if (!sBluetoothAvrcpInterface) {
-    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
-    return JNI_FALSE;
-  }
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  RawAddress* btAddr = (RawAddress*)addr;
-  bt_status_t status = sBluetoothAvrcpInterface->add_to_now_playing_rsp(
-      *btAddr, (btrc_status_t)rspStatus);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed add_to_now_playing_rsp, status: %d", status);
-  }
-  env->ReleaseByteArrayElements(address, addr, 0);
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static JNINativeMethod sMethods[] = {
-    {"classInitNative", "()V", (void*)classInitNative},
-    {"initNative", "()V", (void*)initNative},
-    {"cleanupNative", "()V", (void*)cleanupNative},
-    {"getPlayStatusRspNative", "([BIII)Z", (void*)getPlayStatusRspNative},
-    {"getElementAttrRspNative", "([BB[I[Ljava/lang/String;)Z",
-     (void*)getElementAttrRspNative},
-    {"registerNotificationRspPlayStatusNative", "(II)Z",
-     (void*)registerNotificationRspPlayStatusNative},
-    {"registerNotificationRspTrackChangeNative", "(I[B)Z",
-     (void*)registerNotificationRspTrackChangeNative},
-    {"registerNotificationRspPlayPosNative", "(II)Z",
-     (void*)registerNotificationRspPlayPosNative},
-    {"setVolumeNative", "(I)Z", (void*)setVolumeNative},
-
-    {"setAddressedPlayerRspNative", "([BI)Z",
-     (void*)setAddressedPlayerRspNative},
-
-    {"setBrowsedPlayerRspNative", "([BIBI[Ljava/lang/String;)Z",
-     (void*)setBrowsedPlayerRspNative},
-
-    {"mediaPlayerListRspNative", "([BIIBI[I[B[I[B[S[Ljava/lang/String;)Z",
-     (void*)mediaPlayerListRspNative},
-
-    {"getFolderItemsRspNative",
-     "([BISBI[B[B[B[B[Ljava/lang/String;[I[I[Ljava/lang/String;)Z",
-     (void*)getFolderItemsRspNative},
-
-    {"changePathRspNative", "([BII)Z", (void*)changePathRspNative},
-
-    {"getItemAttrRspNative", "([BIB[I[Ljava/lang/String;)Z",
-     (void*)getItemAttrRspNative},
-
-    {"playItemRspNative", "([BI)Z", (void*)playItemRspNative},
-
-    {"getTotalNumOfItemsRspNative", "([BIII)Z",
-     (void*)getTotalNumOfItemsRspNative},
-
-    {"searchRspNative", "([BIII)Z", (void*)searchRspNative},
-
-    {"addToNowPlayingRspNative", "([BI)Z", (void*)addToNowPlayingRspNative},
-
-    {"registerNotificationRspAddrPlayerChangedNative", "(III)Z",
-     (void*)registerNotificationRspAddrPlayerChangedNative},
-
-    {"registerNotificationRspAvalPlayerChangedNative", "(I)Z",
-     (void*)registerNotificationRspAvalPlayerChangedNative},
-
-    {"registerNotificationRspUIDsChangedNative", "(II)Z",
-     (void*)registerNotificationRspUIDsChangedNative},
-
-    {"registerNotificationRspNowPlayingChangedNative", "(I)Z",
-     (void*)registerNotificationRspNowPlayingChangedNative}};
-
-int register_com_android_bluetooth_avrcp(JNIEnv* env) {
-  return jniRegisterNativeMethods(env, "com/android/bluetooth/avrcp/Avrcp",
-                                  sMethods, NELEM(sMethods));
-}
-
-/* Helper function to copy attributes of item.
- * Assumes that all items in response have same number of attributes
- *
- * returns true on succes, false otherwise.
-*/
-static bool copy_item_attributes(JNIEnv* env, jobject object,
-                                 btrc_folder_items_t* pitem,
-                                 jint* p_attributesIds,
-                                 jobjectArray attributesArray, int item_idx,
-                                 int attribCopiedIndex) {
-  bool success = true;
-
-  /* copy attributes of the item */
-  if (0 < pitem->media.num_attrs) {
-    int num_attrs = pitem->media.num_attrs;
-    ALOGI("%s num_attr = %d", __func__, num_attrs);
-    pitem->media.p_attrs = new btrc_element_attr_val_t[num_attrs];
-    if (!pitem->media.p_attrs) {
-      return false;
-    }
-
-    for (int tempAtrCount = 0; tempAtrCount < pitem->media.num_attrs;
-         ++tempAtrCount) {
-      pitem->media.p_attrs[tempAtrCount].attr_id =
-          p_attributesIds[attribCopiedIndex + tempAtrCount];
-
-      ScopedLocalRef<jstring> text(
-          env, (jstring)env->GetObjectArrayElement(
-                   attributesArray, attribCopiedIndex + tempAtrCount));
-
-      if (!copy_jstring(pitem->media.p_attrs[tempAtrCount].text,
-                        BTRC_MAX_ATTR_STR_LEN, text.get(), env)) {
-        success = false;
-        ALOGE("%s: failed to copy attributes", __func__);
-        break;
-      }
-    }
-  }
-  return success;
-}
-
-/* Helper function to copy String data from java to native
- *
- * returns true on succes, false otherwise
- */
-static bool copy_jstring(uint8_t* str, int maxBytes, jstring jstr,
-                         JNIEnv* env) {
-  if (str == NULL || jstr == NULL || env == NULL) return false;
-
-  memset(str, 0, maxBytes);
-  const char* p_str = env->GetStringUTFChars(jstr, NULL);
-  size_t len = strnlen(p_str, maxBytes - 1);
-  memcpy(str, p_str, len);
-
-  env->ReleaseStringUTFChars(jstr, p_str);
-  return true;
-}
-
-/* Helper function to cleanup items */
-static void cleanup_items(btrc_folder_items_t* p_items, int numItems) {
-  for (int item_idx = 0; item_idx < numItems; item_idx++) {
-    /* release memory for attributes in case item is media item */
-    if ((BTRC_ITEM_MEDIA == p_items[item_idx].item_type) &&
-        p_items[item_idx].media.p_attrs != NULL)
-      delete[] p_items[item_idx].media.p_attrs;
-  }
-}
-}
diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp
index 7710a91..2d9e87b 100644
--- a/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -24,6 +24,7 @@
 #include "utils/Log.h"
 
 #include <string.h>
+#include <shared_mutex>
 
 namespace android {
 static jmethodID method_handlePassthroughRsp;
@@ -46,23 +47,31 @@
 static jmethodID method_handleChangeFolderRsp;
 static jmethodID method_handleSetBrowsedPlayerRsp;
 static jmethodID method_handleSetAddressedPlayerRsp;
+static jmethodID method_handleAddressedPlayerChanged;
+static jmethodID method_handleNowPlayingContentChanged;
 
 static jclass class_MediaBrowser_MediaItem;
 static jclass class_AvrcpPlayer;
 
 static const btrc_ctrl_interface_t* sBluetoothAvrcpInterface = NULL;
 static jobject sCallbacksObj = NULL;
+static std::shared_timed_mutex sCallbacks_mutex;
 
 static void btavrcp_passthrough_response_callback(const RawAddress& bd_addr,
                                                   int id, int pressed) {
   ALOGI("%s: id: %d, pressed: %d", __func__, id, pressed);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
 
   ScopedLocalRef<jbyteArray> addr(
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
   if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for passthrough response");
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
     return;
   }
 
@@ -74,8 +83,13 @@
 
 static void btavrcp_groupnavigation_response_callback(int id, int pressed) {
   ALOGV("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
 
   sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGroupNavigationRsp,
                                (jint)id, (jint)pressed);
@@ -84,13 +98,18 @@
 static void btavrcp_connection_state_callback(bool rc_connect, bool br_connect,
                                               const RawAddress& bd_addr) {
   ALOGI("%s: conn state: rc: %d br: %d", __func__, rc_connect, br_connect);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
 
   ScopedLocalRef<jbyteArray> addr(
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
   if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for connection state");
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
     return;
   }
 
@@ -104,13 +123,18 @@
 static void btavrcp_get_rcfeatures_callback(const RawAddress& bd_addr,
                                             int features) {
   ALOGV("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
 
   ScopedLocalRef<jbyteArray> addr(
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
   if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr ");
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
     return;
   }
 
@@ -123,13 +147,18 @@
 static void btavrcp_setplayerapplicationsetting_rsp_callback(
     const RawAddress& bd_addr, uint8_t accepted) {
   ALOGV("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
 
   ScopedLocalRef<jbyteArray> addr(
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
   if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr ");
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
     return;
   }
 
@@ -144,13 +173,18 @@
     btrc_player_app_attr_t* app_attrs, uint8_t num_ext_attr,
     btrc_player_app_ext_attr_t* ext_attrs) {
   ALOGI("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
 
   ScopedLocalRef<jbyteArray> addr(
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
   if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr ");
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
     return;
   }
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
@@ -168,7 +202,7 @@
   ScopedLocalRef<jbyteArray> playerattribs(
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(arraylen));
   if (!playerattribs.get()) {
-    ALOGE("Fail to new jbyteArray playerattribs ");
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
     return;
   }
 
@@ -191,13 +225,18 @@
 static void btavrcp_playerapplicationsetting_changed_callback(
     const RawAddress& bd_addr, const btrc_player_settings_t& vals) {
   ALOGI("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
 
   ScopedLocalRef<jbyteArray> addr(
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
   if (!addr.get()) {
-    ALOGE("Fail to get new array ");
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
     return;
   }
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
@@ -229,13 +268,18 @@
 static void btavrcp_set_abs_vol_cmd_callback(const RawAddress& bd_addr,
                                              uint8_t abs_vol, uint8_t label) {
   ALOGI("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
 
   ScopedLocalRef<jbyteArray> addr(
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
   if (!addr.get()) {
-    ALOGE("Fail to get new array ");
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
     return;
   }
 
@@ -248,13 +292,18 @@
 static void btavrcp_register_notification_absvol_callback(
     const RawAddress& bd_addr, uint8_t label) {
   ALOGI("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
 
   ScopedLocalRef<jbyteArray> addr(
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
   if (!addr.get()) {
-    ALOGE("Fail to get new array ");
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
     return;
   }
 
@@ -273,13 +322,18 @@
    * Assuming text feild to be null terminated.
    */
   ALOGI("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
 
   ScopedLocalRef<jbyteArray> addr(
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
   if (!addr.get()) {
-    ALOGE("Fail to get new array ");
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
     return;
   }
 
@@ -323,13 +377,18 @@
                                                    uint32_t song_len,
                                                    uint32_t song_pos) {
   ALOGI("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
 
   ScopedLocalRef<jbyteArray> addr(
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
   if (!addr.get()) {
-    ALOGE("Fail to get new array ");
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
     return;
   }
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
@@ -341,13 +400,18 @@
 static void btavrcp_play_status_changed_callback(
     const RawAddress& bd_addr, btrc_play_status_t play_status) {
   ALOGI("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
 
   ScopedLocalRef<jbyteArray> addr(
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
   if (!addr.get()) {
-    ALOGE("Fail to get new array ");
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
     return;
   }
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
@@ -364,8 +428,23 @@
    * counterparts by calling the java constructor for each of the items.
    */
   ALOGV("%s count %d", __func__, count);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
+    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.
@@ -399,17 +478,7 @@
           return;
         }
         // Parse UID
-        ScopedLocalRef<jbyteArray> uidByteArray(
-            sCallbackEnv.get(),
-            sCallbackEnv->NewByteArray(sizeof(uint8_t) * BTRC_UID_SIZE));
-        if (!uidByteArray.get()) {
-          ALOGE("%s can't allocate uid array!", __func__);
-          return;
-        }
-        sCallbackEnv->SetByteArrayRegion(uidByteArray.get(), 0,
-                                         BTRC_UID_SIZE * sizeof(uint8_t),
-                                         (jbyte*)item->media.uid);
-
+        long long uid = *(long long*)item->media.uid;
         // Parse Attrs
         ScopedLocalRef<jintArray> attrIdArray(
             sCallbackEnv.get(),
@@ -435,10 +504,6 @@
           ScopedLocalRef<jstring> attrValStr(
               sCallbackEnv.get(),
               sCallbackEnv->NewStringUTF((char*)(item->media.p_attrs[j].text)));
-          if (!uidByteArray.get()) {
-            ALOGE("%s can't allocate uid array!", __func__);
-            return;
-          }
           sCallbackEnv->SetObjectArrayElement(attrValArray.get(), j,
                                               attrValStr.get());
         }
@@ -446,9 +511,9 @@
         ScopedLocalRef<jobject> mediaObj(
             sCallbackEnv.get(),
             (jobject)sCallbackEnv->CallObjectMethod(
-                sCallbacksObj, method_createFromNativeMediaItem,
-                uidByteArray.get(), (jint)item->media.type, mediaName.get(),
-                attrIdArray.get(), attrValArray.get()));
+                sCallbacksObj, method_createFromNativeMediaItem, uid,
+                (jint)item->media.type, mediaName.get(), attrIdArray.get(),
+                attrValArray.get()));
         if (!mediaObj.get()) {
           ALOGE("%s failed to creae MediaItem for type ITEM_MEDIA", __func__);
           return;
@@ -467,22 +532,12 @@
           return;
         }
         // Parse UID
-        ScopedLocalRef<jbyteArray> uidByteArray(
-            sCallbackEnv.get(),
-            sCallbackEnv->NewByteArray(sizeof(uint8_t) * BTRC_UID_SIZE));
-        if (!uidByteArray.get()) {
-          ALOGE("%s can't allocate uid array!", __func__);
-          return;
-        }
-        sCallbackEnv->SetByteArrayRegion(uidByteArray.get(), 0,
-                                         BTRC_UID_SIZE * sizeof(uint8_t),
-                                         (jbyte*)item->folder.uid);
-
+        long long uid = *(long long*)item->folder.uid;
         ScopedLocalRef<jobject> folderObj(
             sCallbackEnv.get(),
             (jobject)sCallbackEnv->CallObjectMethod(
-                sCallbacksObj, method_createFromNativeFolderItem,
-                uidByteArray.get(), (jint)item->folder.type, folderName.get(),
+                sCallbacksObj, method_createFromNativeFolderItem, uid,
+                (jint)item->folder.type, folderName.get(),
                 (jint)item->folder.playable));
         if (!folderObj.get()) {
           ALOGE("%s failed to create MediaItem for type ITEM_FOLDER", __func__);
@@ -540,43 +595,129 @@
 
   if (isPlayerListing) {
     sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetPlayerItemsRsp,
-                                 itemArray.get());
+                                 addr.get(), itemArray.get());
   } else {
     sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetFolderItemsRsp,
-                                 status, itemArray.get());
+                                 addr.get(), status, itemArray.get());
   }
 }
 
 static void btavrcp_change_path_callback(const RawAddress& bd_addr,
                                          uint32_t count) {
   ALOGI("%s count %d", __func__, count);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)&bd_addr.address);
 
   sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleChangeFolderRsp,
-                               (jint)count);
+                               addr.get(), (jint)count);
 }
 
 static void btavrcp_set_browsed_player_callback(const RawAddress& bd_addr,
                                                 uint8_t num_items,
                                                 uint8_t depth) {
   ALOGI("%s items %d depth %d", __func__, num_items, depth);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)&bd_addr.address);
 
   sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleSetBrowsedPlayerRsp,
-                               (jint)num_items, (jint)depth);
+                               addr.get(), (jint)num_items, (jint)depth);
 }
 
 static void btavrcp_set_addressed_player_callback(const RawAddress& bd_addr,
                                                   uint8_t status) {
   ALOGI("%s status %d", __func__, status);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)&bd_addr.address);
+
+  sCallbackEnv->CallVoidMethod(sCallbacksObj,
+                               method_handleSetAddressedPlayerRsp, addr.get(),
+                               (jint)status);
+}
+
+static void btavrcp_addressed_player_changed_callback(const RawAddress& bd_addr,
+                                                      uint16_t id) {
+  ALOGI("%s status %d", __func__, id);
+  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid()) return;
+  if (!sCallbacksObj) {
+    ALOGE("%s: sCallbacksObj is null", __func__);
+    return;
+  }
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)&bd_addr.address);
+
+  sCallbackEnv->CallVoidMethod(
+      sCallbacksObj, method_handleAddressedPlayerChanged, addr.get(), (jint)id);
+}
+
+static void btavrcp_now_playing_content_changed_callback(
+    const RawAddress& bd_addr) {
+  ALOGI("%s", __func__);
 
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("%s: Failed to allocate a new byte array", __func__);
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)&bd_addr.address);
 
   sCallbackEnv->CallVoidMethod(
-      sCallbacksObj, method_handleSetAddressedPlayerRsp, (jint)status);
+      sCallbacksObj, method_handleNowPlayingContentChanged, addr.get());
 }
 
 static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
@@ -596,7 +737,9 @@
     btavrcp_get_folder_items_callback,
     btavrcp_change_path_callback,
     btavrcp_set_browsed_player_callback,
-    btavrcp_set_addressed_player_callback};
+    btavrcp_set_addressed_player_callback,
+    btavrcp_addressed_player_changed_callback,
+    btavrcp_now_playing_content_changed_callback};
 
 static void classInitNative(JNIEnv* env, jclass clazz) {
   method_handlePassthroughRsp =
@@ -636,32 +779,39 @@
 
   method_handleGetFolderItemsRsp =
       env->GetMethodID(clazz, "handleGetFolderItemsRsp",
-                       "(I[Landroid/media/browse/MediaBrowser$MediaItem;)V");
+                       "([BI[Landroid/media/browse/MediaBrowser$MediaItem;)V");
   method_handleGetPlayerItemsRsp = env->GetMethodID(
       clazz, "handleGetPlayerItemsRsp",
-      "([Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V");
+      "([B[Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V");
 
   method_createFromNativeMediaItem =
       env->GetMethodID(clazz, "createFromNativeMediaItem",
-                       "([BILjava/lang/String;[I[Ljava/lang/String;)Landroid/"
+                       "(JILjava/lang/String;[I[Ljava/lang/String;)Landroid/"
                        "media/browse/MediaBrowser$MediaItem;");
   method_createFromNativeFolderItem = env->GetMethodID(
       clazz, "createFromNativeFolderItem",
-      "([BILjava/lang/String;I)Landroid/media/browse/MediaBrowser$MediaItem;");
+      "(JILjava/lang/String;I)Landroid/media/browse/MediaBrowser$MediaItem;");
   method_createFromNativePlayerItem =
       env->GetMethodID(clazz, "createFromNativePlayerItem",
                        "(ILjava/lang/String;[BII)Lcom/android/bluetooth/"
                        "avrcpcontroller/AvrcpPlayer;");
   method_handleChangeFolderRsp =
-      env->GetMethodID(clazz, "handleChangeFolderRsp", "(I)V");
+      env->GetMethodID(clazz, "handleChangeFolderRsp", "([BI)V");
   method_handleSetBrowsedPlayerRsp =
-      env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "(II)V");
+      env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "([BII)V");
   method_handleSetAddressedPlayerRsp =
-      env->GetMethodID(clazz, "handleSetAddressedPlayerRsp", "(I)V");
+      env->GetMethodID(clazz, "handleSetAddressedPlayerRsp", "([BI)V");
+  method_handleAddressedPlayerChanged =
+      env->GetMethodID(clazz, "handleAddressedPlayerChanged", "([BI)V");
+  method_handleNowPlayingContentChanged =
+      env->GetMethodID(clazz, "handleNowPlayingContentChanged", "([B)V");
+
   ALOGI("%s: succeeds", __func__);
 }
 
 static void initNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
+
   jclass tmpMediaItem =
       env->FindClass("android/media/browse/MediaBrowser$MediaItem");
   class_MediaBrowser_MediaItem = (jclass)env->NewGlobalRef(tmpMediaItem);
@@ -709,6 +859,8 @@
 }
 
 static void cleanupNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
+
   const bt_interface_t* btInf = getBluetoothInterface();
   if (btInf == NULL) {
     ALOGE("Bluetooth module is not loaded");
@@ -961,7 +1113,7 @@
 
 static void changeFolderPathNative(JNIEnv* env, jobject object,
                                    jbyteArray address, jbyte direction,
-                                   jbyteArray uidarr) {
+                                   jlong uid) {
   if (!sBluetoothAvrcpInterface) return;
   jbyte* addr = env->GetByteArrayElements(address, NULL);
   if (!addr) {
@@ -969,22 +1121,22 @@
     return;
   }
 
-  jbyte* uid = env->GetByteArrayElements(uidarr, NULL);
-  if (!uid) {
-    jniThrowIOException(env, EINVAL);
-    return;
-  }
+  // jbyte* uid = env->GetByteArrayElements(uidarr, NULL);
+  // if (!uid) {
+  //  jniThrowIOException(env, EINVAL);
+  //  return;
+  //}
 
   ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
   RawAddress rawAddress;
   rawAddress.FromOctets((uint8_t*)addr);
 
   bt_status_t status = sBluetoothAvrcpInterface->change_folder_path_cmd(
-      rawAddress, (uint8_t)direction, (uint8_t*)uid);
+      rawAddress, (uint8_t)direction, (uint8_t*)&uid);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending changeFolderPathNative command, status: %d", status);
   }
-  env->ReleaseByteArrayElements(address, addr, 0);
+  // env->ReleaseByteArrayElements(address, addr, 0);
 }
 
 static void setBrowsedPlayerNative(JNIEnv* env, jobject object,
@@ -1029,7 +1181,7 @@
 }
 
 static void playItemNative(JNIEnv* env, jobject object, jbyteArray address,
-                           jbyte scope, jbyteArray uidArr, jint uidCounter) {
+                           jbyte scope, jlong uid, jint uidCounter) {
   if (!sBluetoothAvrcpInterface) return;
   jbyte* addr = env->GetByteArrayElements(address, NULL);
   if (!addr) {
@@ -1037,17 +1189,17 @@
     return;
   }
 
-  jbyte* uid = env->GetByteArrayElements(uidArr, NULL);
-  if (!uid) {
-    jniThrowIOException(env, EINVAL);
-    return;
-  }
+  //  jbyte* uid = env->GetByteArrayElements(uidArr, NULL);
+  //  if (!uid) {
+  //    jniThrowIOException(env, EINVAL);
+  //    return;
+  //  }
   RawAddress rawAddress;
   rawAddress.FromOctets((uint8_t*)addr);
 
   ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
   bt_status_t status = sBluetoothAvrcpInterface->play_item_cmd(
-      rawAddress, (uint8_t)scope, (uint8_t*)uid, (uint16_t)uidCounter);
+      rawAddress, (uint8_t)scope, (uint8_t*)&uid, (uint16_t)uidCounter);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending playItemNative command, status: %d", status);
   }
@@ -1071,8 +1223,8 @@
     {"getNowPlayingListNative", "([BII)V", (void*)getNowPlayingListNative},
     {"getFolderListNative", "([BII)V", (void*)getFolderListNative},
     {"getPlayerListNative", "([BII)V", (void*)getPlayerListNative},
-    {"changeFolderPathNative", "([BB[B)V", (void*)changeFolderPathNative},
-    {"playItemNative", "([BB[BI)V", (void*)playItemNative},
+    {"changeFolderPathNative", "([BBJ)V", (void*)changeFolderPathNative},
+    {"playItemNative", "([BBJI)V", (void*)playItemNative},
     {"setBrowsedPlayerNative", "([BI)V", (void*)setBrowsedPlayerNative},
     {"setAddressedPlayerNative", "([BI)V", (void*)setAddressedPlayerNative},
 };
diff --git a/jni/com_android_bluetooth_avrcp_target.cpp b/jni/com_android_bluetooth_avrcp_target.cpp
index 8ac04e4..a6c8d28 100644
--- a/jni/com_android_bluetooth_avrcp_target.cpp
+++ b/jni/com_android_bluetooth_avrcp_target.cpp
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "NewAvrcpTargetJni"
+#define LOG_TAG "AvrcpTargetJni"
 
 #include <base/bind.h>
+#include <base/callback.h>
 #include <map>
 #include <mutex>
 #include <shared_mutex>
@@ -784,10 +785,18 @@
                                j_bdaddr);
 }
 
-static void sendVolumeChangedNative(JNIEnv* env, jobject object, jint volume) {
+static void sendVolumeChangedNative(JNIEnv* env, jobject object,
+                                    jstring address, jint volume) {
+  const char* tmp_addr = env->GetStringUTFChars(address, 0);
+  RawAddress bdaddr;
+  bool success = RawAddress::FromString(tmp_addr, bdaddr);
+  env->ReleaseStringUTFChars(address, tmp_addr);
+
+  if (!success) return;
+
   ALOGD("%s", __func__);
-  for (const auto& cb : volumeCallbackMap) {
-    cb.second.Run(volume & 0x7F);
+  if (volumeCallbackMap.find(bdaddr) != volumeCallbackMap.end()) {
+    volumeCallbackMap.find(bdaddr)->second.Run(volume & 0x7F);
   }
 }
 
@@ -814,7 +823,8 @@
      (void*)connectDeviceNative},
     {"disconnectDeviceNative", "(Ljava/lang/String;)Z",
      (void*)disconnectDeviceNative},
-    {"sendVolumeChangedNative", "(I)V", (void*)sendVolumeChangedNative},
+    {"sendVolumeChangedNative", "(Ljava/lang/String;I)V",
+     (void*)sendVolumeChangedNative},
 };
 
 int register_com_android_bluetooth_avrcp_target(JNIEnv* env) {
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index 976e388..6b4867f 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -40,8 +40,6 @@
 #include <hardware/bluetooth.h>
 #include <mutex>
 
-using base::StringPrintf;
-using bluetooth::Uuid;
 using android::bluetooth::BluetoothSocketManagerBinderServer;
 
 namespace android {
@@ -1217,6 +1215,25 @@
   env->ReleaseByteArrayElements(address, addr, 0);
 }
 
+static jbyteArray obfuscateAddressNative(JNIEnv* env, jobject obj,
+                                         jbyteArray address) {
+  ALOGV("%s", __func__);
+  if (!sBluetoothInterface) return env->NewByteArray(0);
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
+  if (addr == nullptr) {
+    jniThrowIOException(env, EINVAL);
+    return env->NewByteArray(0);
+  }
+  RawAddress addr_obj = {};
+  addr_obj.FromOctets((uint8_t*)addr);
+  std::string output = sBluetoothInterface->obfuscate_address(addr_obj);
+  jsize output_size = output.size() * sizeof(char);
+  jbyteArray output_bytes = env->NewByteArray(output_size);
+  env->SetByteArrayRegion(output_bytes, 0, output_size,
+                          (const jbyte*)output.data());
+  return output_bytes;
+}
+
 static JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */
     {"classInitNative", "()V", (void*)classInitNative},
@@ -1251,7 +1268,8 @@
     {"dumpMetricsNative", "()[B", (void*)dumpMetricsNative},
     {"factoryResetNative", "()Z", (void*)factoryResetNative},
     {"interopDatabaseClearNative", "()V", (void*)interopDatabaseClearNative},
-    {"interopDatabaseAddNative", "(I[BI)V", (void*)interopDatabaseAddNative}};
+    {"interopDatabaseAddNative", "(I[BI)V", (void*)interopDatabaseAddNative},
+    {"obfuscateAddressNative", "([B)[B", (void*)obfuscateAddressNative}};
 
 int register_com_android_bluetooth_btservice_AdapterService(JNIEnv* env) {
   return jniRegisterNativeMethods(
@@ -1306,12 +1324,6 @@
     return JNI_ERR;
   }
 
-  status = android::register_com_android_bluetooth_avrcp(e);
-  if (status < 0) {
-    ALOGE("jni avrcp target registration failure: %d", status);
-    return JNI_ERR;
-  }
-
   status = android::register_com_android_bluetooth_avrcp_target(e);
   if (status < 0) {
     ALOGE("jni new avrcp target registration failure: %d", status);
@@ -1335,12 +1347,6 @@
     return JNI_ERR;
   }
 
-  status = android::register_com_android_bluetooth_hdp(e);
-  if (status < 0) {
-    ALOGE("jni hdp registration failure: %d", status);
-    return JNI_ERR;
-  }
-
   status = android::register_com_android_bluetooth_pan(e);
   if (status < 0) {
     ALOGE("jni pan registration failure: %d", status);
diff --git a/jni/com_android_bluetooth_gatt.cpp b/jni/com_android_bluetooth_gatt.cpp
index 86e667b..4521515 100644
--- a/jni/com_android_bluetooth_gatt.cpp
+++ b/jni/com_android_bluetooth_gatt.cpp
@@ -24,6 +24,7 @@
 #include "utils/Log.h"
 
 #include <base/bind.h>
+#include <base/callback.h>
 #include <string.h>
 #include <array>
 #include <memory>
diff --git a/jni/com_android_bluetooth_hdp.cpp b/jni/com_android_bluetooth_hdp.cpp
deleted file mode 100644
index 3abc243..0000000
--- a/jni/com_android_bluetooth_hdp.cpp
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "BluetoothHealthServiceJni"
-
-#define LOG_NDEBUG 0
-
-#include "android_runtime/AndroidRuntime.h"
-#include "com_android_bluetooth.h"
-#include "hardware/bt_hl.h"
-#include "utils/Log.h"
-
-#include <string.h>
-
-namespace android {
-
-static jmethodID method_onAppRegistrationState;
-static jmethodID method_onChannelStateChanged;
-
-static const bthl_interface_t* sBluetoothHdpInterface = NULL;
-static jobject mCallbacksObj = NULL;
-
-// Define callback functions
-static void app_registration_state_callback(int app_id,
-                                            bthl_app_reg_state_t state) {
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAppRegistrationState,
-                               app_id, (jint)state);
-}
-
-static void channel_state_callback(int app_id, RawAddress* bd_addr,
-                                   int mdep_cfg_index, int channel_id,
-                                   bthl_channel_state_t state, int fd) {
-  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 for channel state");
-    return;
-  }
-
-  // TODO(BT) check if fd is only valid for BTHH_CONN_STATE_CONNECTED state
-  jobject fileDescriptor = NULL;
-  if (state == BTHL_CONN_STATE_CONNECTED) {
-    fileDescriptor = jniCreateFileDescriptor(sCallbackEnv.get(), fd);
-    if (!fileDescriptor) {
-      ALOGE("Failed to convert file descriptor, fd: %d", fd);
-      return;
-    }
-  }
-
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onChannelStateChanged,
-                               app_id, addr.get(), mdep_cfg_index, channel_id,
-                               (jint)state, fileDescriptor);
-}
-
-static bthl_callbacks_t sBluetoothHdpCallbacks = {
-    sizeof(sBluetoothHdpCallbacks), app_registration_state_callback,
-    channel_state_callback};
-
-// Define native functions
-
-static void classInitNative(JNIEnv* env, jclass clazz) {
-  method_onAppRegistrationState =
-      env->GetMethodID(clazz, "onAppRegistrationState", "(II)V");
-  method_onChannelStateChanged = env->GetMethodID(
-      clazz, "onChannelStateChanged", "(I[BIIILjava/io/FileDescriptor;)V");
-  ALOGI("%s: succeeds", __func__);
-}
-
-static void initializeNative(JNIEnv* env, jobject object) {
-  const bt_interface_t* btInf = getBluetoothInterface();
-  if (btInf == NULL) {
-    ALOGE("Bluetooth module is not loaded");
-    return;
-  }
-
-  if (sBluetoothHdpInterface != NULL) {
-    ALOGW("Cleaning up Bluetooth Health Interface before initializing...");
-    sBluetoothHdpInterface->cleanup();
-    sBluetoothHdpInterface = NULL;
-  }
-
-  if (mCallbacksObj != NULL) {
-    ALOGW("Cleaning up Bluetooth Health callback object");
-    env->DeleteGlobalRef(mCallbacksObj);
-    mCallbacksObj = NULL;
-  }
-
-  sBluetoothHdpInterface =
-      (bthl_interface_t*)btInf->get_profile_interface(BT_PROFILE_HEALTH_ID);
-  if (sBluetoothHdpInterface == NULL) {
-    ALOGE("Failed to get Bluetooth Health Interface");
-    return;
-  }
-
-  bt_status_t status = sBluetoothHdpInterface->init(&sBluetoothHdpCallbacks);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed to initialize Bluetooth HDP, status: %d", status);
-    sBluetoothHdpInterface = NULL;
-    return;
-  }
-
-  mCallbacksObj = env->NewGlobalRef(object);
-}
-
-static void cleanupNative(JNIEnv* env, jobject object) {
-  const bt_interface_t* btInf = getBluetoothInterface();
-
-  if (btInf == NULL) {
-    ALOGE("Bluetooth module is not loaded");
-    return;
-  }
-
-  if (sBluetoothHdpInterface != NULL) {
-    ALOGW("Cleaning up Bluetooth Health Interface...");
-    sBluetoothHdpInterface->cleanup();
-    sBluetoothHdpInterface = NULL;
-  }
-
-  if (mCallbacksObj != NULL) {
-    ALOGW("Cleaning up Bluetooth Health object");
-    env->DeleteGlobalRef(mCallbacksObj);
-    mCallbacksObj = NULL;
-  }
-}
-
-static jint registerHealthAppNative(JNIEnv* env, jobject object, jint data_type,
-                                    jint role, jstring name,
-                                    jint channel_type) {
-  if (!sBluetoothHdpInterface) {
-    ALOGE(
-        "Failed to register health app. No Bluetooth Health Interface "
-        "available");
-    return -1;
-  }
-
-  bthl_mdep_cfg_t mdep_cfg;
-  mdep_cfg.mdep_role = (bthl_mdep_role_t)role;
-  mdep_cfg.data_type = data_type;
-  mdep_cfg.channel_type = (bthl_channel_type_t)channel_type;
-  // TODO(BT) pass all the followings in from java instead of reuse name
-  mdep_cfg.mdep_description = env->GetStringUTFChars(name, NULL);
-
-  bthl_reg_param_t reg_param;
-  reg_param.application_name = env->GetStringUTFChars(name, NULL);
-  reg_param.provider_name = NULL;
-  reg_param.srv_name = NULL;
-  reg_param.srv_desp = NULL;
-  reg_param.number_of_mdeps = 1;
-  reg_param.mdep_cfg = &mdep_cfg;
-
-  int app_id;
-  bt_status_t status =
-      sBluetoothHdpInterface->register_application(&reg_param, &app_id);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed register health app, status: %d", status);
-    return -1;
-  }
-
-  env->ReleaseStringUTFChars(name, mdep_cfg.mdep_description);
-  env->ReleaseStringUTFChars(name, reg_param.application_name);
-  return app_id;
-}
-
-static jboolean unregisterHealthAppNative(JNIEnv* env, jobject object,
-                                          int app_id) {
-  if (!sBluetoothHdpInterface) return JNI_FALSE;
-
-  bt_status_t status = sBluetoothHdpInterface->unregister_application(app_id);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed to unregister app %d, status: %d", app_id, status);
-  }
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jint connectChannelNative(JNIEnv* env, jobject object,
-                                 jbyteArray address, jint app_id) {
-  if (!sBluetoothHdpInterface) return -1;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    ALOGE("Bluetooth device address null");
-    return -1;
-  }
-
-  jint chan_id;
-  bt_status_t status = sBluetoothHdpInterface->connect_channel(
-      app_id, (RawAddress*)addr, 0, &chan_id);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed HDP channel connection, status: %d", status);
-    chan_id = -1;
-  }
-  env->ReleaseByteArrayElements(address, addr, 0);
-
-  return chan_id;
-}
-
-static jboolean disconnectChannelNative(JNIEnv* env, jobject object,
-                                        jint channel_id) {
-  if (!sBluetoothHdpInterface) return JNI_FALSE;
-
-  bt_status_t status = sBluetoothHdpInterface->destroy_channel(channel_id);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed disconnect health channel, status: %d", status);
-    return JNI_FALSE;
-  }
-  return JNI_TRUE;
-}
-
-static JNINativeMethod sMethods[] = {
-    {"classInitNative", "()V", (void*)classInitNative},
-    {"initializeNative", "()V", (void*)initializeNative},
-    {"cleanupNative", "()V", (void*)cleanupNative},
-    {"registerHealthAppNative", "(IILjava/lang/String;I)I",
-     (void*)registerHealthAppNative},
-    {"unregisterHealthAppNative", "(I)Z", (void*)unregisterHealthAppNative},
-    {"connectChannelNative", "([BI)I", (void*)connectChannelNative},
-    {"disconnectChannelNative", "(I)Z", (void*)disconnectChannelNative},
-};
-
-int register_com_android_bluetooth_hdp(JNIEnv* env) {
-  return jniRegisterNativeMethods(env,
-                                  "com/android/bluetooth/hdp/HealthService",
-                                  sMethods, NELEM(sMethods));
-}
-}
diff --git a/jni/com_android_bluetooth_hearing_aid.cpp b/jni/com_android_bluetooth_hearing_aid.cpp
index 1602aac..f93a42a 100644
--- a/jni/com_android_bluetooth_hearing_aid.cpp
+++ b/jni/com_android_bluetooth_hearing_aid.cpp
@@ -198,7 +198,6 @@
                                      jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
   if (!sHearingAidInterface) return JNI_FALSE;
-
   jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
     jniThrowIOException(env, EINVAL);
@@ -211,23 +210,6 @@
   return JNI_TRUE;
 }
 
-static jboolean removeFromWhiteListNative(JNIEnv* env, jobject object,
-                                          jbyteArray address) {
-  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sHearingAidInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, nullptr);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  RawAddress* tmpraw = (RawAddress*)addr;
-  sHearingAidInterface->RemoveFromWhiteList(*tmpraw);
-  env->ReleaseByteArrayElements(address, addr, 0);
-  return JNI_TRUE;
-}
-
 static void setVolumeNative(JNIEnv* env, jclass clazz, jint volume) {
   if (!sHearingAidInterface) {
     LOG(ERROR) << __func__
@@ -244,7 +226,6 @@
     {"connectHearingAidNative", "([B)Z", (void*)connectHearingAidNative},
     {"disconnectHearingAidNative", "([B)Z", (void*)disconnectHearingAidNative},
     {"addToWhiteListNative", "([B)Z", (void*)addToWhiteListNative},
-    {"removeFromWhiteListNative", "([B)Z", (void*)removeFromWhiteListNative},
     {"setVolumeNative", "(I)V", (void*)setVolumeNative},
 };
 
diff --git a/jni/com_android_bluetooth_hfp.cpp b/jni/com_android_bluetooth_hfp.cpp
index 8547aa6..f474c28 100644
--- a/jni/com_android_bluetooth_hfp.cpp
+++ b/jni/com_android_bluetooth_hfp.cpp
@@ -38,7 +38,7 @@
 static jmethodID method_onVolumeChanged;
 static jmethodID method_onDialCall;
 static jmethodID method_onSendDtmf;
-static jmethodID method_onNoiceReductionEnable;
+static jmethodID method_onNoiseReductionEnable;
 static jmethodID method_onWBS;
 static jmethodID method_onAtChld;
 static jmethodID method_onAtCnum;
@@ -215,7 +215,7 @@
       ALOGE("Fail to new jbyteArray bd addr for audio state");
       return;
     }
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNoiceReductionEnable,
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNoiseReductionEnable,
                                  nrec == bluetooth::headset::BTHF_NREC_START,
                                  addr.get());
   }
@@ -396,8 +396,8 @@
   method_onDialCall =
       env->GetMethodID(clazz, "onDialCall", "(Ljava/lang/String;[B)V");
   method_onSendDtmf = env->GetMethodID(clazz, "onSendDtmf", "(I[B)V");
-  method_onNoiceReductionEnable =
-      env->GetMethodID(clazz, "onNoiceReductionEnable", "(Z[B)V");
+  method_onNoiseReductionEnable =
+      env->GetMethodID(clazz, "onNoiseReductionEnable", "(Z[B)V");
   method_onWBS = env->GetMethodID(clazz, "onWBS", "(I[B)V");
   method_onAtChld = env->GetMethodID(clazz, "onAtChld", "(I[B)V");
   method_onAtCnum = env->GetMethodID(clazz, "onAtCnum", "([B)V");
@@ -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(),
@@ -804,7 +805,8 @@
 static jboolean phoneStateChangeNative(JNIEnv* env, jobject object,
                                        jint num_active, jint num_held,
                                        jint call_state, jstring number_str,
-                                       jint type, jbyteArray address) {
+                                       jint type, jstring name_str,
+                                       jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
   if (!sBluetoothHfpInterface) {
     ALOGW("%s: sBluetoothHfpInterface is null", __func__);
@@ -817,14 +819,21 @@
     return JNI_FALSE;
   }
   const char* number = env->GetStringUTFChars(number_str, nullptr);
+  const char* name = nullptr;
+  if (name_str != nullptr) {
+    name = env->GetStringUTFChars(name_str, nullptr);
+  }
   bt_status_t status = sBluetoothHfpInterface->PhoneStateChange(
       num_active, num_held, (bluetooth::headset::bthf_call_state_t)call_state,
-      number, (bluetooth::headset::bthf_call_addrtype_t)type,
+      number, (bluetooth::headset::bthf_call_addrtype_t)type, name,
       (RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed report phone state change, status: %d", status);
   }
   env->ReleaseStringUTFChars(number_str, number);
+  if (name != nullptr) {
+    env->ReleaseStringUTFChars(name_str, name);
+  }
   env->ReleaseByteArrayElements(address, addr, 0);
   return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
@@ -908,7 +917,7 @@
     {"atResponseCodeNative", "(II[B)Z", (void*)atResponseCodeNative},
     {"clccResponseNative", "(IIIIZLjava/lang/String;I[B)Z",
      (void*)clccResponseNative},
-    {"phoneStateChangeNative", "(IIILjava/lang/String;I[B)Z",
+    {"phoneStateChangeNative", "(IIILjava/lang/String;ILjava/lang/String;[B)Z",
      (void*)phoneStateChangeNative},
     {"setScoAllowedNative", "(Z)Z", (void*)setScoAllowedNative},
     {"sendBsirNative", "(Z[B)Z", (void*)sendBsirNative},
diff --git a/jni/com_android_bluetooth_hfpclient.cpp b/jni/com_android_bluetooth_hfpclient.cpp
index 3dfc86e..af90922 100644
--- a/jni/com_android_bluetooth_hfpclient.cpp
+++ b/jni/com_android_bluetooth_hfpclient.cpp
@@ -49,6 +49,7 @@
 static jmethodID method_onInBandRing;
 static jmethodID method_onLastVoiceTagNumber;
 static jmethodID method_onRingIndication;
+static jmethodID method_onUnknownEvent;
 
 static jbyteArray marshall_bda(const RawAddress* bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
@@ -322,6 +323,20 @@
                                addr.get());
 }
 
+static void unknown_event_cb(const RawAddress* bd_addr,
+                             const char* eventString) {
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid()) return;
+
+  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+  if (!addr.get()) return;
+
+  ScopedLocalRef<jstring> js_event(sCallbackEnv.get(),
+                                   sCallbackEnv->NewStringUTF(eventString));
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onUnknownEvent,
+                               js_event.get(), addr.get());
+}
+
 static bthf_client_callbacks_t sBluetoothHfpClientCallbacks = {
     sizeof(sBluetoothHfpClientCallbacks),
     connection_state_cb,
@@ -345,6 +360,7 @@
     in_band_ring_cb,
     last_voice_tag_number_cb,
     ring_indication_cb,
+    unknown_event_cb,
 };
 
 static void classInitNative(JNIEnv* env, jclass clazz) {
@@ -377,6 +393,8 @@
   method_onLastVoiceTagNumber =
       env->GetMethodID(clazz, "onLastVoiceTagNumber", "(Ljava/lang/String;[B)V");
   method_onRingIndication = env->GetMethodID(clazz, "onRingIndication", "([B)V");
+  method_onUnknownEvent =
+      env->GetMethodID(clazz, "onUnknownEvent", "(Ljava/lang/String;[B)V");
 
   ALOGI("%s succeeds", __func__);
 }
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/jni/com_android_bluetooth_sdp.cpp b/jni/com_android_bluetooth_sdp.cpp
index c2eb5ce..827db71 100644
--- a/jni/com_android_bluetooth_sdp.cpp
+++ b/jni/com_android_bluetooth_sdp.cpp
@@ -307,6 +307,37 @@
   return handle;
 }
 
+static jint sdpCreatePbapPceRecordNative(JNIEnv* env, jobject obj,
+                                         jstring name_str, jint version) {
+  ALOGD("%s", __func__);
+  if (!sBluetoothSdpInterface) return -1;
+
+  bluetooth_sdp_record record = {};  // Must be zero initialized
+  record.pce.hdr.type = SDP_TYPE_PBAP_PCE;
+
+  const char* service_name = NULL;
+  if (name_str != NULL) {
+    service_name = env->GetStringUTFChars(name_str, NULL);
+    record.pce.hdr.service_name = (char*)service_name;
+    record.pce.hdr.service_name_length = strlen(service_name);
+  } else {
+    record.pce.hdr.service_name = NULL;
+    record.pce.hdr.service_name_length = 0;
+  }
+  record.pce.hdr.profile_version = version;
+
+  int handle = -1;
+  int ret = sBluetoothSdpInterface->create_sdp_record(&record, &handle);
+  if (ret != BT_STATUS_SUCCESS) {
+    ALOGE("SDP Create record failed: %d", ret);
+  } else {
+    ALOGD("SDP Create record success - handle: %d", handle);
+  }
+
+  if (service_name) env->ReleaseStringUTFChars(name_str, service_name);
+  return handle;
+}
+
 static jint sdpCreatePbapPseRecordNative(JNIEnv* env, jobject obj,
                                          jstring name_str, jint scn,
                                          jint l2cap_psm, jint version,
@@ -474,6 +505,8 @@
      (void*)sdpCreateMapMasRecordNative},
     {"sdpCreateMapMnsRecordNative", "(Ljava/lang/String;IIII)I",
      (void*)sdpCreateMapMnsRecordNative},
+    {"sdpCreatePbapPceRecordNative", "(Ljava/lang/String;I)I",
+     (void*)sdpCreatePbapPceRecordNative},
     {"sdpCreatePbapPseRecordNative", "(Ljava/lang/String;IIIII)I",
      (void*)sdpCreatePbapPseRecordNative},
     {"sdpCreateOppOpsRecordNative", "(Ljava/lang/String;III[B)I",
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/lib/room/annotation-1.0.0-beta01.jar b/lib/room/annotation-1.0.0-beta01.jar
new file mode 100644
index 0000000..124f128
--- /dev/null
+++ b/lib/room/annotation-1.0.0-beta01.jar
Binary files differ
diff --git a/lib/room/room-common-2.0.0-beta01.jar b/lib/room/room-common-2.0.0-beta01.jar
new file mode 100644
index 0000000..931d4aa
--- /dev/null
+++ b/lib/room/room-common-2.0.0-beta01.jar
Binary files differ
diff --git a/lib/room/room-compiler-2.0.0-beta01.jar b/lib/room/room-compiler-2.0.0-beta01.jar
new file mode 100644
index 0000000..623a07b
--- /dev/null
+++ b/lib/room/room-compiler-2.0.0-beta01.jar
Binary files differ
diff --git a/lib/room/room-migration-2.0.0-beta01.jar b/lib/room/room-migration-2.0.0-beta01.jar
new file mode 100644
index 0000000..04bca6f
--- /dev/null
+++ b/lib/room/room-migration-2.0.0-beta01.jar
Binary files differ
diff --git a/lib/room/room-runtime-2.0.0-alpha1.aar b/lib/room/room-runtime-2.0.0-alpha1.aar
new file mode 100644
index 0000000..053188b
--- /dev/null
+++ b/lib/room/room-runtime-2.0.0-alpha1.aar
Binary files differ
diff --git a/lib/room/room-testing-2.0.0-alpha1.aar b/lib/room/room-testing-2.0.0-alpha1.aar
new file mode 100644
index 0000000..bb6e62a
--- /dev/null
+++ b/lib/room/room-testing-2.0.0-alpha1.aar
Binary files differ
diff --git a/res/layout/bt_enabling_progress.xml b/res/layout/bt_enabling_progress.xml
index 7f2fd72..0e4f870 100644
--- a/res/layout/bt_enabling_progress.xml
+++ b/res/layout/bt_enabling_progress.xml
@@ -27,7 +27,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
-        <ProgressBar android:id="@+android:id/progress"
+        <ProgressBar android:id="@+id/progress"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content" />
 
diff --git a/res/mipmap-anydpi/bt_share.xml b/res/mipmap-anydpi/bt_share.xml
new file mode 100644
index 0000000..c99e560
--- /dev/null
+++ b/res/mipmap-anydpi/bt_share.xml
@@ -0,0 +1,28 @@
+<!--
+   Copyright (C) 2019 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License
+  -->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+  <foreground>
+    <inset
+      android:drawable="@*android:drawable/ic_bluetooth_share_icon"
+      android:insetTop="25%"
+      android:insetRight="25%"
+      android:insetBottom="25%"
+      android:insetLeft="25%" />
+  </foreground>
+  <background>
+    <color android:color="@android:color/white" />
+  </background>
+</adaptive-icon>
diff --git a/res/mipmap-hdpi/bt_share.png b/res/mipmap-hdpi/bt_share.png
deleted file mode 100644
index 6d16ebf..0000000
--- a/res/mipmap-hdpi/bt_share.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-mdpi/bt_share.png b/res/mipmap-mdpi/bt_share.png
deleted file mode 100644
index 1f514d2..0000000
--- a/res/mipmap-mdpi/bt_share.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-xhdpi/bt_share.png b/res/mipmap-xhdpi/bt_share.png
deleted file mode 100644
index 7bd0e9c..0000000
--- a/res/mipmap-xhdpi/bt_share.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-xxhdpi/bt_share.png b/res/mipmap-xxhdpi/bt_share.png
deleted file mode 100644
index 0fe218e..0000000
--- a/res/mipmap-xxhdpi/bt_share.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-xxxhdpi/bt_share.png b/res/mipmap-xxxhdpi/bt_share.png
deleted file mode 100644
index 7bf313f..0000000
--- a/res/mipmap-xxxhdpi/bt_share.png
+++ /dev/null
Binary files differ
diff --git a/res/raw/silent.wav b/res/raw/silent.wav
new file mode 100755
index 0000000..d6d93ef
--- /dev/null
+++ b/res/raw/silent.wav
Binary files differ
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 4bb0f8a..eb419ed 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -26,7 +26,7 @@
     <string name="airplane_error_title" msgid="2683839635115739939">"وضع الطائرة"</string>
     <string name="airplane_error_msg" msgid="8698965595254137230">"لا يمكنك استخدام البلوتوث في وضع الطائرة."</string>
     <string name="bt_enable_title" msgid="8657832550503456572"></string>
-    <string name="bt_enable_line1" msgid="7203551583048149">"لإستخدام خدمات البلوتوث، يجب تشغيل البلوتوث أولاً."</string>
+    <string name="bt_enable_line1" msgid="7203551583048149">"إلى لاستخدم خدمات البلوتوث، يجب أولاً تشغيل البلوتوث."</string>
     <string name="bt_enable_line2" msgid="4341936569415937994">"هل تريد تشغيل البلوتوث الآن؟"</string>
     <string name="bt_enable_cancel" msgid="1988832367505151727">"إلغاء"</string>
     <string name="bt_enable_ok" msgid="3432462749994538265">"تشغيل"</string>
@@ -100,7 +100,7 @@
     <string name="status_connection_error" msgid="947681831523219891">"لم يتم الاتصال بنجاح."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"لا يمكن معالجة الطلب بشكل صحيح."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"خطأ غير معروف."</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"تم الاستلام عبر بلوتوث"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"تم استلام بلوتوث"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"مشاركة البلوتوث"</string>
     <string name="download_success" msgid="7036160438766730871">"اكتمل استلام <xliff:g id="FILE_SIZE">%1$s</xliff:g>."</string>
     <string name="upload_success" msgid="4014469387779648949">"اكتمل إرسال <xliff:g id="FILE_SIZE">%1$s</xliff:g>."</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index bfb3168..a67ceaa 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ডাউনল’ড মেনেজাৰ ব্যৱহাৰ কৰিব পাৰে।"</string>
+    <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ডাউনল\'ড মেনেজাৰ ব্যৱহাৰ কৰিব পাৰে।"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"এপটোক BluetoothShare মেনেজাৰ ব্যৱহাৰ কৰি ফাইল স্থানান্তৰ কৰিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ব্লুটুথ ডিভাইচ ব্যৱাহৰ কৰিবলৈ স্বীকৃতি দিয়ক।"</string>
     <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ব্যৱহাৰকাৰীৰ অনুমতি অবিহনে এই ডিভাইচক ফাইলবোৰ পঠিয়াবলৈ দি এপটোক ব্লুটুথ ডিভাইচক কিছু সময়ৰ বাবে স্বীকৃতি দিয়ে।"</string>
@@ -39,11 +39,11 @@
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"অন্তৰ্গামী ফাইল"</string>
     <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> পঠাবলৈ সাজু"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ব্লুটুথ শ্বেয়াৰ: <xliff:g id="FILE">%1$s</xliff:g> লাভ কৰি থকা হৈছে"</string>
-    <string name="notification_received" msgid="3324588019186687985">"ব্লুটুথ শ্বেয়াৰ: <xliff:g id="FILE">%1$s</xliff:g> লাভ কৰা হ’ল"</string>
+    <string name="notification_received" msgid="3324588019186687985">"ব্লুটুথ শ্বেয়াৰ: <xliff:g id="FILE">%1$s</xliff:g> লাভ কৰা হ\'ল"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ব্লুটুথ শ্বেয়াৰ: ফাইল <xliff:g id="FILE">%1$s</xliff:g> লাভ কৰা নহ\'ল"</string>
     <string name="notification_sending" msgid="3035748958534983833">"ব্লুটুথ শ্বেয়াৰ: <xliff:g id="FILE">%1$s</xliff:g> প্ৰেৰণ কৰি থকা হৈছে"</string>
-    <string name="notification_sent" msgid="9218710861333027778">"ব্লুটুথ শ্বেয়াৰ: <xliff:g id="FILE">%1$s</xliff:g> প্ৰেৰণ কৰা হ’ল"</string>
-    <string name="notification_sent_complete" msgid="302943281067557969">"১০০% সম্পূৰ্ণ হ’ল"</string>
+    <string name="notification_sent" msgid="9218710861333027778">"ব্লুটুথ শ্বেয়াৰ: <xliff:g id="FILE">%1$s</xliff:g> প্ৰেৰণ কৰা হ\'ল"</string>
+    <string name="notification_sent_complete" msgid="302943281067557969">"১০০% সম্পূৰ্ণ হ\'ল"</string>
     <string name="notification_sent_fail" msgid="6696082233774569445">"ব্লুটুথ শ্বেয়াৰ: ফাইল <xliff:g id="FILE">%1$s</xliff:g> প্ৰেৰণ কৰা নহ\'ল"</string>
     <string name="download_title" msgid="3353228219772092586">"ফাইল স্থানান্তৰণ"</string>
     <string name="download_line1" msgid="4926604799202134144">"প্ৰেৰক: \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
@@ -60,12 +60,12 @@
     <string name="download_fail_line2" msgid="8950394574689971071">"ফাইল: <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="download_fail_line3" msgid="3451040656154861722">"কাৰণ: <xliff:g id="REASON">%1$s</xliff:g>"</string>
     <string name="download_fail_ok" msgid="1521733664438320300">"ঠিক"</string>
-    <string name="download_succ_line5" msgid="4509944688281573595">"ফাইল লাভ কৰা হ’ল"</string>
+    <string name="download_succ_line5" msgid="4509944688281573595">"ফাইল লাভ কৰা হ\'ল"</string>
     <string name="download_succ_ok" msgid="7053688246357050216">"খোলক"</string>
     <string name="upload_line1" msgid="2055952074059709052">"প্ৰতি: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="upload_line3" msgid="4920689672457037437">"ফাইলৰ প্ৰকাৰ: <xliff:g id="TYPE">%1$s</xliff:g> (<xliff:g id="SIZE">%2$s</xliff:g>)"</string>
     <string name="upload_line5" msgid="7759322537674229752">"ফাইল প্ৰেৰণ কৰি থকা হৈছে…"</string>
-    <string name="upload_succ_line5" msgid="5687317197463383601">"ফাইল প্ৰেৰণ কৰা হ’ল"</string>
+    <string name="upload_succ_line5" msgid="5687317197463383601">"ফাইল প্ৰেৰণ কৰা হ\'ল"</string>
     <string name="upload_succ_ok" msgid="7705428476405478828">"ঠিক"</string>
     <string name="upload_fail_line1" msgid="7899394672421491701">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"লৈ ফাইলটো পঠিয়াব পৰা নগ\'ল।"</string>
     <string name="upload_fail_line1_2" msgid="2108129204050841798">"ফাইল: <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -80,17 +80,17 @@
     <string name="enabling_progress_content" msgid="4601542238119927904">"ব্লুটুথ অন কৰি থকা হৈছে…"</string>
     <string name="bt_toast_1" msgid="972182708034353383">"ফাইলটো লাভ কৰা হ\'ব। জাননী পেনেলত অগ্ৰগতি নিৰীক্ষণ কৰক।"</string>
     <string name="bt_toast_2" msgid="8602553334099066582">"ফাইলটো লাভ কৰিব নোৱাৰি।"</string>
-    <string name="bt_toast_3" msgid="6707884165086862518">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ৰ ফাইল লাভ কৰা বন্ধ কৰা হ’ল"</string>
+    <string name="bt_toast_3" msgid="6707884165086862518">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ৰ ফাইল লাভ কৰা বন্ধ কৰা হ\'ল"</string>
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"লৈ ফাইল প্ৰেৰণ কৰি থকা হৈছে"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"লৈ <xliff:g id="NUMBER">%1$s</xliff:g>টা ফাইল পঠিয়াই থকা হৈছে"</string>
-    <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"লৈ ফাইল পঠিওৱা বন্ধ কৰা হ’ল"</string>
+    <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"লৈ ফাইল পঠিওৱা বন্ধ কৰা হ\'ল"</string>
     <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ৰ পৰা লাভ কৰা ফাইলটো ছেভ কৰিবলৈ ইউএছবি সঞ্চয়াগাৰত পৰ্যাপ্ত খালী ঠাই নাই"</string>
     <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ৰ পৰা লাভ কৰা ফাইলটো ছেভ কৰিবলৈ এছডি কাৰ্ডত পৰ্যাপ্ত খালী ঠাই নাই"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"ইমান খালী ঠাইৰ দৰকাৰ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"বহুত বেছি অনুৰোধৰ ওপৰত প্ৰক্ৰিয়া চলি আছে৷ পিছত আকৌ চেষ্টা কৰক৷"</string>
     <string name="status_pending" msgid="2503691772030877944">"ফাইলৰ স্থানান্তৰণ এতিয়ালৈকে আৰম্ভ হোৱা নাই।"</string>
     <string name="status_running" msgid="6562808920311008696">"ফাইলৰ স্থানান্তৰণ চলি আছে।"</string>
-    <string name="status_success" msgid="239573225847565868">"ফাইলৰ স্থানান্তৰণৰ কাৰ্য সফলতাৰে সম্পন্ন কৰা হ’ল।"</string>
+    <string name="status_success" msgid="239573225847565868">"ফাইলৰ স্থানান্তৰণৰ কাৰ্য সফলতাৰে সম্পন্ন কৰা হ\'ল।"</string>
     <string name="status_not_accept" msgid="1695082417193780738">"সমল সমৰ্থিত নহয়।"</string>
     <string name="status_forbidden" msgid="613956401054050725">"নিৰ্দিষ্ট কৰা ডিভাইচটোৱে স্থানান্তৰণ নিষিদ্ধ কৰিছে।"</string>
     <string name="status_canceled" msgid="6664490318773098285">"ব্যৱহাৰকাৰীয়ে স্থানান্তৰণ বাতিল কৰিছে।"</string>
@@ -100,10 +100,10 @@
     <string name="status_connection_error" msgid="947681831523219891">"সংযোগ কৰিব পৰা নগ\'ল।"</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"অনুৰোধ সঠিকভাৱে পৰিচালনা কৰিব নোৱাৰি।"</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"অজ্ঞাত আসোঁৱাহ।"</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"ব্লুটুথ লাভ কৰা হ’ল"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"ব্লুটুথ লাভ কৰা হ\'ল"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"ব্লুটুথ শ্বেয়াৰ"</string>
-    <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> লাভ কৰা কাৰ্য সম্পূৰ্ণ হ’ল।"</string>
-    <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> প্ৰেৰণ কৰা কাম সম্পূৰ্ণ হ’ল"</string>
+    <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> লাভ কৰা কাৰ্য সম্পূৰ্ণ হ\'ল।"</string>
+    <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> প্ৰেৰণ কৰা কাম সম্পূৰ্ণ হ\'ল"</string>
     <string name="inbound_history_title" msgid="6940914942271327563">"অন্তৰ্গামী স্থানান্তৰণসমূহ"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"বহিৰ্গামী স্থানান্তৰণসমূহ"</string>
     <string name="no_transfers" msgid="3482965619151865672">"স্থানান্তৰণৰ ইতিহাস খালী আছে।"</string>
@@ -111,12 +111,12 @@
     <string name="outbound_noti_title" msgid="8051906709452260849">"ব্লুটুথ শ্বেয়াৰ: প্ৰেৰণ কৰা ফাইলসমূহ"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"ব্লুটুথ শ্বেয়াৰ: লাভ কৰা ফাইলসমূহ"</string>
     <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
-      <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g>টা অসফল হ’ল।</item>
-      <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g>টা অসফল হ’ল।</item>
+      <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g>টা অসফল হ\'ল।</item>
+      <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g>টা অসফল হ\'ল।</item>
     </plurals>
     <plurals name="noti_caption_success" formatted="false" msgid="1572472450257645181">
-      <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g>টা সফল হ’ল, %2$s</item>
-      <item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g>টা সফল হ’ল, %2$s</item>
+      <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g>টা সফল হ\'ল, %2$s</item>
+      <item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g>টা সফল হ\'ল, %2$s</item>
     </plurals>
     <string name="transfer_menu_clear_all" msgid="790017462957873132">"সূচী মচক"</string>
     <string name="transfer_menu_open" msgid="3368984869083107200">"খোলক"</string>
@@ -129,8 +129,8 @@
     <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"এপ্লিকেশ্বন আইকন"</string>
     <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ব্লুটুথৰ জৰিয়তে বাৰ্তা শ্বেয়াৰ কৰাৰ ছেটিংসমূহ"</string>
     <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"একাউণ্ট বাছনি কৰিব নোৱাৰি। ০টা শ্লটবোৰ বাকী আছে"</string>
-    <string name="bluetooth_connected" msgid="6718623220072656906">"ব্লুটুথ অডিঅ’ সংযুক্ত কৰা হ’ল"</string>
-    <string name="bluetooth_disconnected" msgid="3318303728981478873">"ব্লুটুথ অডিঅ\'ৰ সৈতে সংযোগ বিচ্ছিন্ন কৰা হ’ল"</string>
+    <string name="bluetooth_connected" msgid="6718623220072656906">"ব্লুটুথ অডিঅ’ সংযুক্ত কৰা হ\'ল"</string>
+    <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">"৪ জি. বি. তকৈ ডাঙৰ ফাইল স্থানান্তৰ কৰিব নোৱাৰি"</string>
 </resources>
diff --git a/res/values-az/strings_map.xml b/res/values-az/strings_map.xml
deleted file mode 100644
index 0b8584e..0000000
--- a/res/values-az/strings_map.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="map_session_key_dialog_title" msgid="4357431314165953502">"%1$s üçün seans açarı daxil edin"</string>
-    <string name="map_session_key_dialog_header" msgid="1858363785687455516">"Bluetooth seans açarı tələb olunur"</string>
-    <string name="map_acceptance_timeout_message" msgid="2247454902690565876">"%1$s ilə bağlantı qəbul edərkən fasilə yarandı"</string>
-    <string name="map_authentication_timeout_message" msgid="2151423397615327066">"%1$s ilə seans açarını daxil edərkən fasilə yarandı"</string>
-    <string name="map_auth_notif_ticker" msgid="8840174807927327119">"Obex təsdiqləmə sorğusu"</string>
-    <string name="map_auth_notif_title" msgid="301767019364765129">"Seans Açarı"</string>
-    <string name="map_auth_notif_message" msgid="1510095655064214863">"%1$s üçün seans açarı daxil edin"</string>
-    <string name="map_defaultname" msgid="3805234816465216759">"Maşın dəsti"</string>
-    <string name="map_unknownName" msgid="4985352365863687360">"Naməlum ad"</string>
-    <string name="map_localPhoneName" msgid="6264496105941797768">"Mənim adım"</string>
-    <string name="map_defaultnumber" msgid="3838264444043503815">"000000"</string>
-</resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 3dc12a2..2f07816 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -100,7 +100,7 @@
     <string name="status_connection_error" msgid="947681831523219891">"Povezivanje nije uspjelo."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"Nije moguće pravilno obraditi zahtjev."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"Nepoznata greška."</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"Primljeno putem Bluetootha"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Primljeno preko Bluetootha"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"Dijeljenje putem Bluetootha"</string>
     <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Primanje završeno."</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Slanje dovršeno."</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index f77534d..0418fbc 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -45,7 +45,7 @@
     <string name="notification_sent" msgid="9218710861333027778">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> enviat"</string>
     <string name="notification_sent_complete" msgid="302943281067557969">"100% complet"</string>
     <string name="notification_sent_fail" msgid="6696082233774569445">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> no enviat"</string>
-    <string name="download_title" msgid="3353228219772092586">"Transferència de fitxers"</string>
+    <string name="download_title" msgid="3353228219772092586">"Transferència del fitxer"</string>
     <string name="download_line1" msgid="4926604799202134144">"De: \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="download_line2" msgid="5876973543019417712">"Fitxer: <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="download_line3" msgid="4384821622908676061">"Mida del fitxer: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
diff --git a/res/values-da/strings_pbap.xml b/res/values-da/strings_pbap.xml
index 4837260..ae570c6 100644
--- a/res/values-da/strings_pbap.xml
+++ b/res/values-da/strings_pbap.xml
@@ -1,16 +1,16 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"Angiv sessionsnøgle til %1$s"</string>
+    <string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"Indtast sessionsnøgle til %1$s"</string>
     <string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"Bluetooth-sessionsnøgle kræves"</string>
     <string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"Der var timeout ved forbindelsen med %1$s"</string>
     <string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"Der var timeout i indgangssessionnøgle med %1$s"</string>
     <string name="auth_notif_ticker" msgid="1575825798053163744">"Anmodning om Obex-godkendelse"</string>
     <string name="auth_notif_title" msgid="7599854855681573258">"Sessionstast"</string>
-    <string name="auth_notif_message" msgid="6667218116427605038">"Angiv sessionsnøgle til %1$s"</string>
+    <string name="auth_notif_message" msgid="6667218116427605038">"Indtast sessionsnøgle til %1$s"</string>
     <string name="defaultname" msgid="4821590500649090078">"Bilsæt"</string>
     <string name="unknownName" msgid="2841414754740600042">"Ukendt navn"</string>
     <string name="localPhoneName" msgid="2349001318925409159">"Mit navn"</string>
     <string name="defaultnumber" msgid="8520116145890867338">"000000"</string>
-    <string name="pbap_notification_group" msgid="8487669554703627168">"Deling af kontakter via Bluetooth"</string>
+    <string name="pbap_notification_group" msgid="8487669554703627168">"Deling af kontaktpersoner via Bluetooth"</string>
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index c8b882e..df7233a 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -87,7 +87,7 @@
     <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Der USB-Speicher verfügt nicht über genügend Speicherplatz, um die Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\" zu speichern."</string>
     <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Die SD-Karte verfügt über zu wenig Speicherplatz für die Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Erforderlicher Speicherplatz: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
-    <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Es werden zurzeit zu viele Anfragen verarbeitet. Bitte versuche es später noch einmal."</string>
+    <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Es werden zurzeit zu viele Anfragen verarbeitet. Versuche es später erneut."</string>
     <string name="status_pending" msgid="2503691772030877944">"Die Dateiübertragung wurde noch nicht gestartet."</string>
     <string name="status_running" msgid="6562808920311008696">"Dateiübertragung läuft."</string>
     <string name="status_success" msgid="239573225847565868">"Die Dateiübertragung wurde abgeschlossen."</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index efd1453..ba19b9c 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -23,8 +23,8 @@
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Unknown device"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Unknown"</string>
-    <string name="airplane_error_title" msgid="2683839635115739939">"Airplane mode"</string>
-    <string name="airplane_error_msg" msgid="8698965595254137230">"You can\'t use Bluetooth in Airplane mode."</string>
+    <string name="airplane_error_title" msgid="2683839635115739939">"Aeroplane mode"</string>
+    <string name="airplane_error_msg" msgid="8698965595254137230">"You can\'t use Bluetooth in Aeroplane mode."</string>
     <string name="bt_enable_title" msgid="8657832550503456572"></string>
     <string name="bt_enable_line1" msgid="7203551583048149">"To use Bluetooth services, you must first turn on Bluetooth."</string>
     <string name="bt_enable_line2" msgid="4341936569415937994">"Turn on Bluetooth now?"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 1b142d4..ddbb688 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -100,7 +100,7 @@
     <string name="status_connection_error" msgid="947681831523219891">"Conexión incorrecta"</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"No se puede procesar la solicitud correctamente."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"Error desconocido"</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"Recibido por Bluetooth"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Recibidos por Bluetooth"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"Compartir por Bluetooth"</string>
     <string name="download_success" msgid="7036160438766730871">"Se recibieron <xliff:g id="FILE_SIZE">%1$s</xliff:g> completos."</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> enviados por completo."</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 413a4e1..e87bd01 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -100,7 +100,7 @@
     <string name="status_connection_error" msgid="947681831523219891">"Ühendus ebaõnnestus."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"Taotlust ei saa õigesti käsitleda."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"Tundmatu viga."</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetoothiga vastu võetud"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth vastu võetud"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"Jagamine Bluetoothiga"</string>
     <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> vastuvõtmine lõpetatud."</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> saatmine lõpetatud."</string>
diff --git a/res/values-et/strings_map.xml b/res/values-et/strings_map.xml
deleted file mode 100644
index 8fe1642..0000000
--- a/res/values-et/strings_map.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="map_session_key_dialog_title" msgid="4357431314165953502">"Sisestage seansivõti seadmele %1$s"</string>
-    <string name="map_session_key_dialog_header" msgid="1858363785687455516">"Vajalik Bluetoothi seansivõti"</string>
-    <string name="map_acceptance_timeout_message" msgid="2247454902690565876">"Esines ajalõpp ühenduse aktsepteerimiseks seadmega %1$s"</string>
-    <string name="map_authentication_timeout_message" msgid="2151423397615327066">"Esines ajalõpp seansivõtme sisestamiseks seadmele %1$s"</string>
-    <string name="map_auth_notif_ticker" msgid="8840174807927327119">"Obexi autentimise taotlus"</string>
-    <string name="map_auth_notif_title" msgid="301767019364765129">"Seansivõti"</string>
-    <string name="map_auth_notif_message" msgid="1510095655064214863">"Sisestage seansivõti seadmele %1$s"</string>
-    <string name="map_defaultname" msgid="3805234816465216759">"Autokomplekt"</string>
-    <string name="map_unknownName" msgid="4985352365863687360">"Tundmatu nimi"</string>
-    <string name="map_localPhoneName" msgid="6264496105941797768">"Minu nimi"</string>
-    <string name="map_defaultnumber" msgid="3838264444043503815">"000000"</string>
-</resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index af1fc3f..b975c56 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -100,7 +100,7 @@
     <string name="status_connection_error" msgid="947681831523219891">"Ezin izan da konektatu."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"Ezin da eskaera behar bezala kudeatu."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"Errore ezezaguna."</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth bidez jasotakoak"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth bidezko elementua jaso da"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"Bluetooth bidez partekatzea"</string>
     <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> jaso dira osorik."</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> osorik bidali da."</string>
diff --git a/res/values-fa/strings_pbap.xml b/res/values-fa/strings_pbap.xml
index 348156a..8282443 100644
--- a/res/values-fa/strings_pbap.xml
+++ b/res/values-fa/strings_pbap.xml
@@ -3,8 +3,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"‏تایپ کلید جلسه برای %1$s"</string>
     <string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"به کلید جلسه بلوتوث نیاز است"</string>
-    <string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"‏پذیرش اتصال با %1$s خیلی زمان برد"</string>
-    <string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"‏وارد کردن کلید جلسه با %1$s خیلی زمان برد"</string>
+    <string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"‏هنگام پذیرش اتصال به %1$s وقفه زمانی ایجاد شده است"</string>
+    <string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"‏هنگام ورود کلید جلسه با %1$s وقفه زمانی ایجاد شده است"</string>
     <string name="auth_notif_ticker" msgid="1575825798053163744">"‏درخواست احراز هویت Obex"</string>
     <string name="auth_notif_title" msgid="7599854855681573258">"کلید جلسه"</string>
     <string name="auth_notif_message" msgid="6667218116427605038">"‏تایپ کلید جلسه برای %1$s"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 6860f15..8460d36 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -100,13 +100,13 @@
     <string name="status_connection_error" msgid="947681831523219891">"કનેક્શન અસફળ."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"વિનંતી યોગ્ય રીતે હેન્ડલ કરી શકાતી નથી."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"અજાણી ભૂલ."</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"બ્લૂટૂથથી મળેલી ફાઇલો"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"બ્લૂટૂથ પ્રાપ્ત"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"બ્લૂટૂથ શેર"</string>
     <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> પ્રાપ્તિ પૂર્ણ"</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> મોકલવું પૂર્ણ."</string>
     <string name="inbound_history_title" msgid="6940914942271327563">"ઇન્બાઉન્ડ સ્થાનાંતરણો"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"આઉટબાઉન્ડ સ્થાનાંતરણ"</string>
-    <string name="no_transfers" msgid="3482965619151865672">"કંઈપણ ટ્રાન્સફર કરવામાં આવ્યું નથી."</string>
+    <string name="no_transfers" msgid="3482965619151865672">"સ્થાનાંતરણ ઇતિહાસ ખાલી છે."</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"સૂચિમાંથી તમામ આઇટમ્સ સાફ કરવામાં આવશે."</string>
     <string name="outbound_noti_title" msgid="8051906709452260849">"બ્લૂટૂથ શેર: મોકલેલી ફાઇલો"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"બ્લૂટૂથ શેર: પ્રાપ્ત ફાઇલો"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 9e66d3b..4ed2477 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -100,13 +100,13 @@
     <string name="status_connection_error" msgid="947681831523219891">"कनेक्‍शन विफल."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"अनुरोध को सही तरह से प्रबंधित नहीं किया जा सकता."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"अज्ञात गड़बड़ी‍."</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"ब्लूटूथ से मिली फ़ाइलें"</string>
-    <string name="opp_notification_group" msgid="3486303082135789982">"ब्लूटूथ के ज़रिए शेयर"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"ब्लूटूथ प्राप्त"</string>
+    <string name="opp_notification_group" msgid="3486303082135789982">"ब्लूटूथ के ज़रिए साझा"</string>
     <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> प्राप्ति पूर्ण."</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> भेजना पूर्ण."</string>
     <string name="inbound_history_title" msgid="6940914942271327563">"इनबाउंड स्थानांतरण"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"आउटबाउंड स्थानांतरण"</string>
-    <string name="no_transfers" msgid="3482965619151865672">"कुछ भी ट्रांसफ़र नहीं किया गया."</string>
+    <string name="no_transfers" msgid="3482965619151865672">"स्थानांतरण इतिहास खाली है."</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"सूची से सभी आइटम साफ़ कर दिए जाएंगे."</string>
     <string name="outbound_noti_title" msgid="8051906709452260849">"ब्लूटूथ शेयर: भेजी गई फ़ाइलें"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"ब्लूटूथ शेयर: प्राप्त फ़ाइलें"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 54396de..60dfe89 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -100,7 +100,7 @@
     <string name="status_connection_error" msgid="947681831523219891">"Միացումը անհաջող էր:"</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"Հարցումը հնարավոր չէ ճշգրտորեն մշակել:"</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"Անհայտ սխալ:"</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"Ստացված է Bluetooth-ով"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth-ը ստացված է"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"Bluetooth համօգտագործում"</string>
     <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> ստացումն ավարտված է:"</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> ուղարկումն ավարտված է:"</string>
diff --git a/res/values-hy/strings_map.xml b/res/values-hy/strings_map.xml
deleted file mode 100644
index 91f2078..0000000
--- a/res/values-hy/strings_map.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="map_session_key_dialog_title" msgid="4357431314165953502">"Մուտքագրեք աշխատաշրջանի բանալին %1$s-ի համար"</string>
-    <string name="map_session_key_dialog_header" msgid="1858363785687455516">"Պահանջվում է Bluetooth-ի աշխատաշրջանի բանալի"</string>
-    <string name="map_acceptance_timeout_message" msgid="2247454902690565876">"%1$s-ի հետ կապի ընդունման ժամանակը սպառվեց"</string>
-    <string name="map_authentication_timeout_message" msgid="2151423397615327066">"%1$s-ով աշխատաշրջանի բանալու մուտքագրման ժամանակը սպառվեց"</string>
-    <string name="map_auth_notif_ticker" msgid="8840174807927327119">"Կասեցնել նույնականացման հարցումը"</string>
-    <string name="map_auth_notif_title" msgid="301767019364765129">"Աշխատաշրջանի բանալի"</string>
-    <string name="map_auth_notif_message" msgid="1510095655064214863">"Մուտքագրեք աշխատաշրջանի բանալին %1$s-ի համար"</string>
-    <string name="map_defaultname" msgid="3805234816465216759">"Carkit"</string>
-    <string name="map_unknownName" msgid="4985352365863687360">"Անհայտ անուն"</string>
-    <string name="map_localPhoneName" msgid="6264496105941797768">"Իմ անունը"</string>
-    <string name="map_defaultnumber" msgid="3838264444043503815">"000000"</string>
-</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 602b60e..8a6396a 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -78,7 +78,7 @@
     <string name="not_exist_file_desc" msgid="4059531573790529229">"הקובץ לא קיים. \n"</string>
     <string name="enabling_progress_title" msgid="436157952334723406">"המתן..."</string>
     <string name="enabling_progress_content" msgid="4601542238119927904">"‏מפעיל Bluetooth…"</string>
-    <string name="bt_toast_1" msgid="972182708034353383">"הקובץ יתקבל. יש לבדוק את ההתקדמות בלוח ההתראות."</string>
+    <string name="bt_toast_1" msgid="972182708034353383">"הקובץ יתקבל. בדוק את ההתקדמות בחלונית \'הודעות\'."</string>
     <string name="bt_toast_2" msgid="8602553334099066582">"לא ניתן לקבל את הקובץ."</string>
     <string name="bt_toast_3" msgid="6707884165086862518">"הופסקה קבלת קובץ מאת \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="bt_toast_4" msgid="4678812947604395649">"שולח קובץ אל \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index e4bff90..8b91ce0 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -100,7 +100,7 @@
     <string name="status_connection_error" msgid="947681831523219891">"接続できませんでした。"</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"リクエストを正しく処理できません。"</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"不明なエラーです。"</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth で受信"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetoothで受信"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"Bluetooth 共有"</string>
     <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g>の受信が完了しました。"</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g>の送信が完了しました。"</string>
diff --git a/res/values-ka/strings_map.xml b/res/values-ka/strings_map.xml
deleted file mode 100644
index 8e645c4..0000000
--- a/res/values-ka/strings_map.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="map_session_key_dialog_title" msgid="4357431314165953502">"სესიის გასაღები %1$s-ისთვის"</string>
-    <string name="map_session_key_dialog_header" msgid="1858363785687455516">"აუცილებელია Bluetooth სესიის გასაღები"</string>
-    <string name="map_acceptance_timeout_message" msgid="2247454902690565876">"%1$s-თან კავშირის მიღების დრო ამოიწურა"</string>
-    <string name="map_authentication_timeout_message" msgid="2151423397615327066">"%1$s-თან სესიის გასაღების შეყვანის დრო ამოიწურა"</string>
-    <string name="map_auth_notif_ticker" msgid="8840174807927327119">"Obex ავთენტიფიკაციის მოთხოვნა"</string>
-    <string name="map_auth_notif_title" msgid="301767019364765129">"სესიის გასაღები"</string>
-    <string name="map_auth_notif_message" msgid="1510095655064214863">"სესიის გასაღები %1$s-ისთვის"</string>
-    <string name="map_defaultname" msgid="3805234816465216759">"მანქანის ნაკრები"</string>
-    <string name="map_unknownName" msgid="4985352365863687360">"უცნობი სახელი"</string>
-    <string name="map_localPhoneName" msgid="6264496105941797768">"ჩემი სახელი"</string>
-    <string name="map_defaultnumber" msgid="3838264444043503815">"000000"</string>
-</resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index bb91d3e..aaefa04 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -100,7 +100,7 @@
     <string name="status_connection_error" msgid="947681831523219891">"Байланыс сәтсіз болды."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"Өтінішті дұрыс орындау мүмкін емес."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"Белгісіз қателік."</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth арқылы алынғандар"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth қабылданды"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"Bluetooth бөлісу"</string>
     <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Толығымен қабылданды."</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Жіберіліп болды."</string>
diff --git a/res/values-km/strings_map.xml b/res/values-km/strings_map.xml
deleted file mode 100644
index 8118669..0000000
--- a/res/values-km/strings_map.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="map_session_key_dialog_title" msgid="4357431314165953502">"បញ្ចូល​សោ​សម័យ​សម្រាប់ %1$s"</string>
-    <string name="map_session_key_dialog_header" msgid="1858363785687455516">"បាន​ទាមទារ​សោ​សម័យ​ប៊្លូធូស"</string>
-    <string name="map_acceptance_timeout_message" msgid="2247454902690565876">"អស់​ពេល​វេលា​ក្នុង​ការ​ទទួល​យក​ការ​តភ្ជាប់​ជាមួយ %1$s"</string>
-    <string name="map_authentication_timeout_message" msgid="2151423397615327066">"អស់​ពេល​​ដើម្បី​បញ្ចូល​សោ​សម័យ​ជាមួយ %1$s"</string>
-    <string name="map_auth_notif_ticker" msgid="8840174807927327119">"សំណើ​ការ​ផ្ទៀងផ្ទាត់ Obex"</string>
-    <string name="map_auth_notif_title" msgid="301767019364765129">"សោ​សម័យ"</string>
-    <string name="map_auth_notif_message" msgid="1510095655064214863">"បញ្ចូល​សោ​សម័យ​សម្រាប់ %1$s"</string>
-    <string name="map_defaultname" msgid="3805234816465216759">"Carkit"</string>
-    <string name="map_unknownName" msgid="4985352365863687360">"មិន​ស្គាល់​ឈ្មោះ"</string>
-    <string name="map_localPhoneName" msgid="6264496105941797768">"ឈ្មោះ​របស់​ខ្ញុំ"</string>
-    <string name="map_defaultnumber" msgid="3838264444043503815">"000000"</string>
-</resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 64c1ede..0d9521e 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -100,13 +100,13 @@
     <string name="status_connection_error" msgid="947681831523219891">"Туташуу ийгиликсиз."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"Сурамды туура иштетүү мүмкүн эмес."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"Белгисиз ката."</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth аркылуу алынгандар"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth кабыл алгандар"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"Bluetooth аркылуу бөлүшүү"</string>
     <string name="download_success" msgid="7036160438766730871">"Бардыгы кабыл алынды: <xliff:g id="FILE_SIZE">%1$s</xliff:g>."</string>
     <string name="upload_success" msgid="4014469387779648949">"Бардыгы жөнөтүлдү: <xliff:g id="FILE_SIZE">%1$s</xliff:g>."</string>
     <string name="inbound_history_title" msgid="6940914942271327563">"Кириш өткөрүүлөр"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"Чыгуучу өткөрүүлөр"</string>
-    <string name="no_transfers" msgid="3482965619151865672">"Эч нерсе алына элек"</string>
+    <string name="no_transfers" msgid="3482965619151865672">"Өткөрүү тарыхы бош."</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"Тизмек толугу менен тазаланат."</string>
     <string name="outbound_noti_title" msgid="8051906709452260849">"Bluetooth бөлүшүү: Файлдар жөнөтүлдү"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"Bluetooth бөлүшүү: Алынган файлдар"</string>
@@ -128,7 +128,7 @@
     <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Калган көзөнөктөр:"</string>
     <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Колдонмонун сөлөкөтү"</string>
     <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth билдирүү бөлүшүү жөндөөлөрү"</string>
-    <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Аккаунт тандалбай жатат: 0 орун калды"</string>
+    <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Каттоо эсебин тандоо мүмкүн эмес. 0 көзөнөк калды"</string>
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth аудио туташты"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth аудио ажыратылды"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth аудио"</string>
diff --git a/res/values-lo/strings_map.xml b/res/values-lo/strings_map.xml
deleted file mode 100644
index dd3a288..0000000
--- a/res/values-lo/strings_map.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="map_session_key_dialog_title" msgid="4357431314165953502">"ພິມລະຫັດເຊສຊັນສຳລັບ %1$s"</string>
-    <string name="map_session_key_dialog_header" msgid="1858363785687455516">"ຕ້ອງມີ Bluetooth ລະຫັດເຊສຊັນ"</string>
-    <string name="map_acceptance_timeout_message" msgid="2247454902690565876">"ໝົດເວລາທີ່ຈະຮັບການເຊື່ອມຕໍ່ກັບ %1$s"</string>
-    <string name="map_authentication_timeout_message" msgid="2151423397615327066">"ເກີດໝົດເວລາກ່ອນທີ່ຈະໃສ່ລະຫັດເຊສຊັນກັບ %1$s"</string>
-    <string name="map_auth_notif_ticker" msgid="8840174807927327119">"ການຮ້ອງຂໍພິສູດຢືນຢັນໂຕ Obex"</string>
-    <string name="map_auth_notif_title" msgid="301767019364765129">"ກະແຈເຊສຊັສ"</string>
-    <string name="map_auth_notif_message" msgid="1510095655064214863">"ພິມລະຫັດເຊສຊັນສຳລັບ %1$s"</string>
-    <string name="map_defaultname" msgid="3805234816465216759">"Carkit"</string>
-    <string name="map_unknownName" msgid="4985352365863687360">"ບໍ່ຮູ້ຊື່"</string>
-    <string name="map_localPhoneName" msgid="6264496105941797768">"ຊື່ຂອງຂ້ອຍ"</string>
-    <string name="map_defaultnumber" msgid="3838264444043503815">"000000"</string>
-</resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index c6ec941..6936fd7 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -100,7 +100,7 @@
     <string name="status_connection_error" msgid="947681831523219891">"Врската е неуспешна."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"Барањето не може да се обработи правилно."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"Непозната грешка."</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"Прием од Bluetooth"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Примен Bluetooth"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"Споделено преку Bluetooth"</string>
     <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Примањето е завршено."</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Праќањето е завршено."</string>
diff --git a/res/values-mn/strings_map.xml b/res/values-mn/strings_map.xml
deleted file mode 100644
index 45fc07d..0000000
--- a/res/values-mn/strings_map.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="map_session_key_dialog_title" msgid="4357431314165953502">"%1$s-н горимын түлхүүрийг оруулна уу"</string>
-    <string name="map_session_key_dialog_header" msgid="1858363785687455516">"Блютүүт горимын түлхүүр шаардлагатай"</string>
-    <string name="map_acceptance_timeout_message" msgid="2247454902690565876">"%1$s-тай холболт хийхийг зөвшөөрөх явцад хугацаа хэтэрсэн байна"</string>
-    <string name="map_authentication_timeout_message" msgid="2151423397615327066">"%1$s-д горимын түлхүүр оруулах явцад хугацаа хэтэрсэн байна"</string>
-    <string name="map_auth_notif_ticker" msgid="8840174807927327119">"Obex гэрчлэлтийн хүсэлт"</string>
-    <string name="map_auth_notif_title" msgid="301767019364765129">"Горимын Түлхүүр"</string>
-    <string name="map_auth_notif_message" msgid="1510095655064214863">"%1$s-н горимын түлхүүрийг оруулна уу"</string>
-    <string name="map_defaultname" msgid="3805234816465216759">"Carkit"</string>
-    <string name="map_unknownName" msgid="4985352365863687360">"Тодорхойгүй нэр"</string>
-    <string name="map_localPhoneName" msgid="6264496105941797768">"Миний нэр"</string>
-    <string name="map_defaultnumber" msgid="3838264444043503815">"000000"</string>
-</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 345f44f..a5b2b39 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -26,7 +26,7 @@
     <string name="airplane_error_title" msgid="2683839635115739939">"विमान मोड"</string>
     <string name="airplane_error_msg" msgid="8698965595254137230">"तुम्ही विमान मोड मध्ये ब्लूटूथ वापरू शकत नाही."</string>
     <string name="bt_enable_title" msgid="8657832550503456572"></string>
-    <string name="bt_enable_line1" msgid="7203551583048149">"ब्लूटूथ सेवांचा वापर करण्‍यासाठी, तुम्ही प्रथम ब्लूटूथ चालू करा."</string>
+    <string name="bt_enable_line1" msgid="7203551583048149">"ब्लूटुथ सेवांचा वापर करण्‍यासाठी, आपण प्रथम ब्लूटुथ चालू करा."</string>
     <string name="bt_enable_line2" msgid="4341936569415937994">"आता ब्लूटूथ चालू करायचे?"</string>
     <string name="bt_enable_cancel" msgid="1988832367505151727">"रद्द करा"</string>
     <string name="bt_enable_ok" msgid="3432462749994538265">"चालू करा"</string>
@@ -73,7 +73,7 @@
     <string name="upload_fail_cancel" msgid="9118496285835687125">"बंद करा"</string>
     <string name="bt_error_btn_ok" msgid="5965151173011534240">"ठीक"</string>
     <string name="unknown_file" msgid="6092727753965095366">"अज्ञात फाइल"</string>
-    <string name="unknown_file_desc" msgid="480434281415453287">"या प्रकारची फाइल हाताळण्यासाठी कोणताही अ‍ॅप नाही. \n"</string>
+    <string name="unknown_file_desc" msgid="480434281415453287">"या प्रकारची फाइल हाताळण्यासाठी कोणताही अॅप नाही. \n"</string>
     <string name="not_exist_file" msgid="3489434189599716133">"फाइल नाही"</string>
     <string name="not_exist_file_desc" msgid="4059531573790529229">"फाइल अस्‍तित्वात नाही. \n"</string>
     <string name="enabling_progress_title" msgid="436157952334723406">"कृपया प्रतीक्षा करा..."</string>
@@ -84,8 +84,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ना फाइल पाठवित आहे"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" ना <xliff:g id="NUMBER">%1$s</xliff:g> फायली पाठवित आहे"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ना फाइल पाठविणे थांबविले"</string>
-    <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" कडील फाइल सेव्ह करण्‍यासाठी USB संचयनात पुरेसे स्‍थान नाही"</string>
-    <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" कडील फाइल सेव्ह करण्‍यासाठी SD कार्डवर पुरेसे स्‍थान नाही"</string>
+    <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" कडील फाइल जतन करण्‍यासाठी USB संचयनात पुरेसे स्‍थान नाही"</string>
+    <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" कडील फाइल जतन करण्‍यासाठी SD कार्डवर पुरेसे स्‍थान नाही"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"स्‍थान आवश्यक: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"बर्‍याच विनंत्यांवर प्रक्रिया होत आहे. नंतर पुन्हा प्रयत्न करा."</string>
     <string name="status_pending" msgid="2503691772030877944">"फाइल स्थानांतरणाचा अद्याप प्रारंभ झाला नाही."</string>
@@ -96,7 +96,7 @@
     <string name="status_canceled" msgid="6664490318773098285">"वापरकर्त्याने स्‍थानांतरण रद्द केले."</string>
     <string name="status_file_error" msgid="3671917770630165299">"संचयन समस्‍या."</string>
     <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB स्टोरेज नाही."</string>
-    <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD कार्ड नाही. स्‍थानांतरित केलेल्‍या फायली सेव्ह करण्‍यासाठी SD कार्ड घाला."</string>
+    <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD कार्ड नाही. स्‍थानांतरित केलेल्‍या फायली जतन करण्‍यासाठी SD कार्ड घाला."</string>
     <string name="status_connection_error" msgid="947681831523219891">"कनेक्‍शन अयशस्‍वी."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"विनंती योग्य रीतीने हाताळली जाऊ शकत नाही."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"अज्ञात एरर"</string>
@@ -106,7 +106,7 @@
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> पाठविणे पूर्ण."</string>
     <string name="inbound_history_title" msgid="6940914942271327563">"इनबाउंड स्‍थानांतरणे"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"आउटबाउंड स्‍थानांतरणे"</string>
-    <string name="no_transfers" msgid="3482965619151865672">"ट्रांसफर इतिहास रिक्त आहे."</string>
+    <string name="no_transfers" msgid="3482965619151865672">"स्‍थानांतरण इतिहास रिक्त आहे."</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"सूचीमधून सर्व आयटम साफ केले जातील."</string>
     <string name="outbound_noti_title" msgid="8051906709452260849">"ब्लूटूथ शेअर: पाठवलेल्या फायली"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"ब्लूटूथ शेअर: मिळवलेल्‍या फायली"</string>
@@ -124,10 +124,10 @@
     <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"साफ करा"</string>
     <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"सेव्ह करा"</string>
     <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"रद्द करा"</string>
-    <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"तुम्ही ब्लूटूथद्वारे शेअर करू इच्छित असलेली खाती निवडा. कनेक्ट करताना अद्याप तुम्ही खात्यांमधील कोणताही अॅक्सेस स्वीकारण्याची आवश्यकता आहे."</string>
+    <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"तुम्ही ब्लूटूथद्वारे सामायिक करू इच्छित असलेली खाती निवडा. कनेक्ट करताना अद्याप तुम्ही खात्यांमधील कोणताही अॅक्सेस स्वीकारण्याची आवश्यकता आहे."</string>
     <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"स्लॉट शिल्लक:"</string>
     <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"अॅप्लिकेशन आयकन"</string>
-    <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ब्लूटूथ मेसेज सामायिकरण सेटिंग्ज"</string>
+    <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ब्लूटूथ संदेश सामायिकरण सेटिंग्ज"</string>
     <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"खाते निवडू शकत नाही. 0 स्लॉट शिल्लक"</string>
     <string name="bluetooth_connected" msgid="6718623220072656906">"ब्लूटूथ ऑडिओ कनेक्ट केला"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ब्लूटूथ ऑडिओ डिस्कनेक्ट केला"</string>
diff --git a/res/values-ms/strings_map.xml b/res/values-ms/strings_map.xml
deleted file mode 100644
index afc98d7..0000000
--- a/res/values-ms/strings_map.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="map_session_key_dialog_title" msgid="4357431314165953502">"Taipkan kunci sesi untuk %1$s"</string>
-    <string name="map_session_key_dialog_header" msgid="1858363785687455516">"Kunci sesi Bluetooth diperlukan"</string>
-    <string name="map_acceptance_timeout_message" msgid="2247454902690565876">"Berlaku tamat masa semasa menerima sambungan dengan %1$s"</string>
-    <string name="map_authentication_timeout_message" msgid="2151423397615327066">"Tamat masa berlaku semasa memasukkan kunci sesi dengan %1$s"</string>
-    <string name="map_auth_notif_ticker" msgid="8840174807927327119">"Permintaan pengesahan Obex"</string>
-    <string name="map_auth_notif_title" msgid="301767019364765129">"Kunci Sesi"</string>
-    <string name="map_auth_notif_message" msgid="1510095655064214863">"Taipkan kunci sesi untuk %1$s"</string>
-    <string name="map_defaultname" msgid="3805234816465216759">"Kit kereta"</string>
-    <string name="map_unknownName" msgid="4985352365863687360">"Nama tidak diketahui"</string>
-    <string name="map_localPhoneName" msgid="6264496105941797768">"Nama saya"</string>
-    <string name="map_defaultnumber" msgid="3838264444043503815">"000000"</string>
-</resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index cfdea66..5865087 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -22,7 +22,7 @@
     <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"အက်ပ်အား ဘလူးတုသ်စက်ကို ယာယီခွင့်ပြုစာရင်းထဲ ထည့်ရန်ခွင့်ပြုကာ အသုံးပြုသူ၏ အတည်ပြုချက်မရယူပဲ ဖိုင်များကို စက်ထဲသို့ ပို့ခွင့်ပြုမည်"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"ဘလူးတုသ်"</string>
     <string name="unknown_device" msgid="9221903979877041009">"မသိသော စက်"</string>
-    <string name="unknownNumber" msgid="4994750948072751566">"မသိပါ"</string>
+    <string name="unknownNumber" msgid="4994750948072751566">"အကြောင်းအရာ မသိရှိ"</string>
     <string name="airplane_error_title" msgid="2683839635115739939">"လေယာဉ်ပျံမုဒ်"</string>
     <string name="airplane_error_msg" msgid="8698965595254137230">"လေယာဥ်ပျံပေါ်သုံးစနစ်တွင် ဘလူးတုသ်အသုံးပြုမရပါ"</string>
     <string name="bt_enable_title" msgid="8657832550503456572"></string>
@@ -96,7 +96,7 @@
     <string name="status_canceled" msgid="6664490318773098285">"အသုံးပြုသူမှ လွှဲပြောင်းခြင်းကို ဖယ်ဖျက်ရန်"</string>
     <string name="status_file_error" msgid="3671917770630165299">"သိုလှောင်မှု အချက်"</string>
     <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USBသိုလှောင်ရာမရှိပါ"</string>
-    <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SDကဒ်မရှိပါ၊ လွှဲပြောင်းထားသော ဖိုင်များကို သိမ်းရန် SDကဒ်ထည့်ပါ"</string>
+    <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SDကဒ်မရှိပါ၊ လွှဲပြောင်းထားသော ဖိုင်များကို သိမ်းဆည်းရန် SDကဒ်ထည့်ပါ"</string>
     <string name="status_connection_error" msgid="947681831523219891">"ချိတ်ဆက်ခြင်း မအောင်မြင်ပါ"</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"တောင်းခံခြင်းကို မှန်ကန်စွာကိုင်တွယ်မရပါ"</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"အမည်မသိသော မှားယွင်းမှု"</string>
@@ -106,7 +106,7 @@
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> ပို့ခြင်း ပြီးဆုံးပြီး"</string>
     <string name="inbound_history_title" msgid="6940914942271327563">"ပြန်လည်ရောက်လာသော လွှဲမှုများ"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"ပြန်လည်ထွက်မည့် လွှဲမှုများ"</string>
-    <string name="no_transfers" msgid="3482965619151865672">"လွဲပြောင်းခြင်း မှတ်တမ်းတွင် ဘာမျှမရှိပါ"</string>
+    <string name="no_transfers" msgid="3482965619151865672">"လွဲပြောင်းခြင်း မှတ်တမ်းတွင် ဘာမှမရှိပါ"</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"အရာများအားလုံး စာရင်းမှ ရှင်းလင်းပစ်လိမ့်မည်"</string>
     <string name="outbound_noti_title" msgid="8051906709452260849">"ဘလူးတုသ် ဝေမျှမှု - ဖိုင်ပို့ပြီး"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"ဘလူးတုသ် ဝေမျှမှု - ရရှိပြီးဖိုင်များ"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 24c8a16..996c0b7 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -34,7 +34,7 @@
     <string name="incoming_file_confirm_content" msgid="2752605552743148036">"आगमन फाइल स्वीकार गर्नुहुन्छ?"</string>
     <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"अस्वीकार गर्नुहोस्"</string>
     <string name="incoming_file_confirm_ok" msgid="281462442932231475">"स्वीकार्नुहोस्"</string>
-    <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ठिक छ"</string>
+    <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ठीक छ"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" बाट आगमन फाइल स्वीकार्दा समय सकिएको थियो"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"आगमन फाइल"</string>
     <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ले <xliff:g id="FILE">%2$s</xliff:g> पठाउन तयार छ"</string>
@@ -59,19 +59,19 @@
     <string name="download_fail_line1" msgid="3846450148862894552">"फाइल प्राप्त भएन"</string>
     <string name="download_fail_line2" msgid="8950394574689971071">"फाइल: <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="download_fail_line3" msgid="3451040656154861722">"कारण: <xliff:g id="REASON">%1$s</xliff:g>"</string>
-    <string name="download_fail_ok" msgid="1521733664438320300">"ठिक छ"</string>
+    <string name="download_fail_ok" msgid="1521733664438320300">"ठीक छ"</string>
     <string name="download_succ_line5" msgid="4509944688281573595">"फाइल प्राप्त भयो"</string>
     <string name="download_succ_ok" msgid="7053688246357050216">"खोल्नुहोस्"</string>
     <string name="upload_line1" msgid="2055952074059709052">"लाई:\"<xliff:g id="RECIPIENT">%1$s</xliff:g> \""</string>
     <string name="upload_line3" msgid="4920689672457037437">"फाइल प्रकार: <xliff:g id="TYPE">%1$s</xliff:g> ( <xliff:g id="SIZE">%2$s</xliff:g> )"</string>
     <string name="upload_line5" msgid="7759322537674229752">"फाइल पठाउँदै..."</string>
     <string name="upload_succ_line5" msgid="5687317197463383601">"फाइल पठाइयो"</string>
-    <string name="upload_succ_ok" msgid="7705428476405478828">"ठिक छ"</string>
+    <string name="upload_succ_ok" msgid="7705428476405478828">"ठीक छ"</string>
     <string name="upload_fail_line1" msgid="7899394672421491701">"फाइल\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" लाई पठाइएन।"</string>
     <string name="upload_fail_line1_2" msgid="2108129204050841798">"फाइल: <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="upload_fail_ok" msgid="5807702461606714296">"फेरि प्रयास गर्नुहोस्"</string>
     <string name="upload_fail_cancel" msgid="9118496285835687125">"बन्द गर्नुहोस्"</string>
-    <string name="bt_error_btn_ok" msgid="5965151173011534240">"ठिक छ"</string>
+    <string name="bt_error_btn_ok" msgid="5965151173011534240">"ठीक छ"</string>
     <string name="unknown_file" msgid="6092727753965095366">"अज्ञात फाइल"</string>
     <string name="unknown_file_desc" msgid="480434281415453287">"यस प्रकारको फाइल सम्हालन  कुनै अनुप्रयोग छैन।\n"</string>
     <string name="not_exist_file" msgid="3489434189599716133">"फाइल छैन"</string>
diff --git a/res/values-ne/strings_map.xml b/res/values-ne/strings_map.xml
deleted file mode 100644
index 7584c1e..0000000
--- a/res/values-ne/strings_map.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="map_session_key_dialog_title" msgid="4357431314165953502">"%1$s का लागि सत्र कुञ्जी टाइप गर्नुहोस्"</string>
-    <string name="map_session_key_dialog_header" msgid="1858363785687455516">"ब्लूटूथ सत्र कुञ्जी आवश्यक"</string>
-    <string name="map_acceptance_timeout_message" msgid="2247454902690565876">"%1$s सँग जडान स्वीकार गर्न समय सकियो"</string>
-    <string name="map_authentication_timeout_message" msgid="2151423397615327066">"%1$s का साथ सत्र कुञ्जी इनपुट गर्ने समय सकियो"</string>
-    <string name="map_auth_notif_ticker" msgid="8840174807927327119">"Obex प्रमाणिकरण अनुरोध"</string>
-    <string name="map_auth_notif_title" msgid="301767019364765129">"सत्र कुञ्जी"</string>
-    <string name="map_auth_notif_message" msgid="1510095655064214863">"%1$s का लागि सत्र कुञ्जी टाइप गर्नुहोस्"</string>
-    <string name="map_defaultname" msgid="3805234816465216759">"Carkit"</string>
-    <string name="map_unknownName" msgid="4985352365863687360">"अज्ञात नाम"</string>
-    <string name="map_localPhoneName" msgid="6264496105941797768">"मेरो नाम"</string>
-    <string name="map_defaultnumber" msgid="3838264444043503815">"००००००"</string>
-</resources>
diff --git a/res/values-ne/test_strings.xml b/res/values-ne/test_strings.xml
index df5a45f..ce2bc40 100644
--- a/res/values-ne/test_strings.xml
+++ b/res/values-ne/test_strings.xml
@@ -6,7 +6,7 @@
     <string name="update_record" msgid="2480425402384910635">"रेकर्ड निश्चित गर्नुहोस्"</string>
     <string name="ack_record" msgid="6716152390978472184">"Ack रेकर्ड"</string>
     <string name="deleteAll_record" msgid="4383349788485210582">"सबै रेकर्ड मेटाउनुहोस्"</string>
-    <string name="ok_button" msgid="6519033415223065454">"ठिक छ"</string>
+    <string name="ok_button" msgid="6519033415223065454">"ठीक छ"</string>
     <string name="delete_record" msgid="4645040331967533724">"रेकर्ड मेटाउनुहोस्"</string>
     <string name="start_server" msgid="9034821924409165795">"TCP सर्भर सुरु गर्नुहोस्"</string>
     <string name="notify_server" msgid="4369106744022969655">"TCP सर्भर सूचित गर्नुहोस्"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 39e300d..24a48e8 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Er is een time-out opgetreden bij het accepteren van een inkomend bestand van \'<xliff:g id="SENDER">%1$s</xliff:g>\'"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Inkomend bestand"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> is klaar om <xliff:g id="FILE">%2$s</xliff:g> te verzenden"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> is gereed om <xliff:g id="FILE">%2$s</xliff:g> te verzenden"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Delen via Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> ontvangen"</string>
     <string name="notification_received" msgid="3324588019186687985">"Delen via Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> ontvangen"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Delen via Bluetooth: bestand <xliff:g id="FILE">%1$s</xliff:g> niet ontvangen"</string>
diff --git a/res/values-or/config.xml b/res/values-or/config.xml
deleted file mode 100644
index 4f96544..0000000
--- a/res/values-or/config.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2009-2012 Broadcom Corporation
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="pairing_ui_package" msgid="6399948348712579121">"com.android.settings"</string>
-</resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
deleted file mode 100644
index b93a78d..0000000
--- a/res/values-or/strings.xml
+++ /dev/null
@@ -1,136 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2007 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ଡାଉନଲୋଡ୍‌ ମ୍ୟାନେଜର୍‌କୁ ଆକ୍ସେସ୍‌ କରନ୍ତୁ।"</string>
-    <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"BluetoothShare ମ୍ୟାନେଜର୍‌ ଆକ୍ସେସ୍‌ କରି ଫାଇଲ୍‌ଗୁଡ଼ିକ ଟ୍ରାନ୍ସଫର୍‌ କରିବାକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପ୍‌କୁ ଅନୁମତି ଦିଅନ୍ତୁ।"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ଡିଭାଇସ୍‌ ଆକ୍ସେସ୍‌କୁ ସ୍ୱୀକୃତି ଦିଅନ୍ତୁ।"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ୟୁଜର୍‌ଙ୍କ ସୁନିଶ୍ଚିତତା ବିନା ଏହି ଡିଭାଇସ୍‌କୁ ଫାଇଲ୍‌ ପଠାଇବା ନିମନ୍ତେ ଗୋଟିଏ ବ୍ଲୁ-ଟୂଥ୍‍‌ ଡିଭାଇସ୍‌କୁ ଅନୁମତି ଦେଇ କିଛି ସମୟ ପାଇଁ ଆପ୍‌କୁ ସ୍ୱୀକୃତି ଦେଇଥାଏ।"</string>
-    <string name="bt_share_picker_label" msgid="6268100924487046932">"ବ୍ଲୁ-ଟୂଥ୍‍‌"</string>
-    <string name="unknown_device" msgid="9221903979877041009">"ଅଜଣା ଡିଭାଇସ୍"</string>
-    <string name="unknownNumber" msgid="4994750948072751566">"ଅଜଣା"</string>
-    <string name="airplane_error_title" msgid="2683839635115739939">"ଏରୋପ୍ଲେନ୍‍ ମୋଡ୍"</string>
-    <string name="airplane_error_msg" msgid="8698965595254137230">"ଆପଣ, ଏୟାରପ୍ଲେନ୍‌ ମୋଡ୍‌ରେ ବ୍ଲୁ-ଟୂଥ୍‍‌ ବ୍ୟବହାର କରିପାରିବେ ନାହିଁ।"</string>
-    <string name="bt_enable_title" msgid="8657832550503456572"></string>
-    <string name="bt_enable_line1" msgid="7203551583048149">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ସେବା ବ୍ୟବହାର କରିବା ପାଇଁ, ଆପଣଙ୍କୁ ପ୍ରଥମେ ବ୍ଲୁ-ଟୂଥ୍‍‌ ଅନ୍‌ କରିବାକୁ ପଡ଼ିବ।"</string>
-    <string name="bt_enable_line2" msgid="4341936569415937994">"ବ୍ଲୁ-ଟୁଥ୍‌କୁ ଏବେ ଅନ୍‌ କରିବେ?"</string>
-    <string name="bt_enable_cancel" msgid="1988832367505151727">"କ୍ୟାନ୍ସଲ୍‍"</string>
-    <string name="bt_enable_ok" msgid="3432462749994538265">"ଅନ୍ କରନ୍ତୁ"</string>
-    <string name="incoming_file_confirm_title" msgid="8139874248612182627">"ଫାଇଲ୍‌ ଟ୍ରାନ୍ସଫର୍‌ କରନ୍ତୁ"</string>
-    <string name="incoming_file_confirm_content" msgid="2752605552743148036">"ଆସୁଥିବା ଫାଇଲ୍‌କୁ ସ୍ୱୀକାର କରିବେ?"</string>
-    <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"ଅସ୍ୱୀକାର"</string>
-    <string name="incoming_file_confirm_ok" msgid="281462442932231475">"ସ୍ୱୀକାର କରନ୍ତୁ"</string>
-    <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ଠିକ୍‌ ଅଛି"</string>
-    <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କଠାରୁ ଆସୁଥିବା ଫାଇଲ୍‌ ସ୍ୱୀକାର କରୁଥିବାବେଳେ ସମୟ ସମାପ୍ତ ହୋଇଗଲା"</string>
-    <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ଆସୁଥିବା ଫାଇଲ୍‌"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="FILE">%2$s</xliff:g> ପଠାଇବା ପାଇଁ <xliff:g id="SENDER">%1$s</xliff:g> ପ୍ରସ୍ତୁତ"</string>
-    <string name="notification_receiving" msgid="4674648179652543984">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ଶେୟାର୍‌: <xliff:g id="FILE">%1$s</xliff:g> ପ୍ରାପ୍ତ କରୁଛି"</string>
-    <string name="notification_received" msgid="3324588019186687985">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ଶେୟାର୍‌: <xliff:g id="FILE">%1$s</xliff:g> ପ୍ରାପ୍ତ କରାଯାଇଛି"</string>
-    <string name="notification_received_fail" msgid="3619350997285714746">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ଶେୟାର୍‌: <xliff:g id="FILE">%1$s</xliff:g> ଫାଇଲ୍‌ ପ୍ରାପ୍ତ କରାଯାଇନାହିଁ"</string>
-    <string name="notification_sending" msgid="3035748958534983833">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ଶେୟାର୍‌: <xliff:g id="FILE">%1$s</xliff:g> ପଠାଉଛି"</string>
-    <string name="notification_sent" msgid="9218710861333027778">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ଶେୟାର୍‌: <xliff:g id="FILE">%1$s</xliff:g> ପଠାଗଲା"</string>
-    <string name="notification_sent_complete" msgid="302943281067557969">"100% ସମ୍ପୂର୍ଣ୍ଣ"</string>
-    <string name="notification_sent_fail" msgid="6696082233774569445">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ଶେୟାର୍‌: <xliff:g id="FILE">%1$s</xliff:g> ଫାଇଲ୍‌ ପଠାଯାଇନାହିଁ"</string>
-    <string name="download_title" msgid="3353228219772092586">"ଫାଇଲ୍‌ ଟ୍ରାନ୍ସଫର୍‌ କରନ୍ତୁ"</string>
-    <string name="download_line1" msgid="4926604799202134144">"ପ୍ରେରକ: \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="download_line2" msgid="5876973543019417712">"ଫାଇଲ୍‌: <xliff:g id="FILE">%1$s</xliff:g>"</string>
-    <string name="download_line3" msgid="4384821622908676061">"ଫାଇଲ୍‌ ଆକାର: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
-    <string name="download_line4" msgid="8535996869722666525"></string>
-    <string name="download_line5" msgid="3069560415845295386">"ଫାଇଲ୍‌ ପ୍ରାପ୍ତ କରୁଛି…"</string>
-    <string name="download_cancel" msgid="9177305996747500768">"ବନ୍ଦ କରନ୍ତୁ"</string>
-    <string name="download_ok" msgid="5000360731674466039">"ଲୁଚାନ୍ତୁ"</string>
-    <string name="incoming_line1" msgid="2127419875681087545">"ପ୍ରେରକ"</string>
-    <string name="incoming_line2" msgid="3348994249285315873">"ଫାଇଲ୍ ନାମ"</string>
-    <string name="incoming_line3" msgid="7954237069667474024">"ଆକାର"</string>
-    <string name="download_fail_line1" msgid="3846450148862894552">"ଫାଇଲ୍‌ ପ୍ରାପ୍ତ ହେଲା ନାହିଁ"</string>
-    <string name="download_fail_line2" msgid="8950394574689971071">"ଫାଇଲ୍‌: <xliff:g id="FILE">%1$s</xliff:g>"</string>
-    <string name="download_fail_line3" msgid="3451040656154861722">"କାରଣ: <xliff:g id="REASON">%1$s</xliff:g>"</string>
-    <string name="download_fail_ok" msgid="1521733664438320300">"ଠିକ୍‌ ଅଛି"</string>
-    <string name="download_succ_line5" msgid="4509944688281573595">"ଫାଇଲ୍‌ ପ୍ରାପ୍ତ ହେଲା"</string>
-    <string name="download_succ_ok" msgid="7053688246357050216">"ଖୋଲନ୍ତୁ"</string>
-    <string name="upload_line1" msgid="2055952074059709052">"ପ୍ରାପ୍ତକର୍ତ୍ତା: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="upload_line3" msgid="4920689672457037437">"ଫାଇଲ୍‌ ପ୍ରକାର: <xliff:g id="TYPE">%1$s</xliff:g> (<xliff:g id="SIZE">%2$s</xliff:g>)"</string>
-    <string name="upload_line5" msgid="7759322537674229752">"ଫାଇଲ୍‌ ପଠାଯାଉଛି…"</string>
-    <string name="upload_succ_line5" msgid="5687317197463383601">"ଫାଇଲ୍‌ ପଠାଗଲା"</string>
-    <string name="upload_succ_ok" msgid="7705428476405478828">"ଠିକ୍‌ ଅଛି"</string>
-    <string name="upload_fail_line1" msgid="7899394672421491701">"ଫାଇଲ୍‌ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ଙ୍କୁ ପଠାଯାଇନଥିଲା।"</string>
-    <string name="upload_fail_line1_2" msgid="2108129204050841798">"ଫାଇଲ୍‌: <xliff:g id="FILE">%1$s</xliff:g>"</string>
-    <string name="upload_fail_ok" msgid="5807702461606714296">"ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
-    <string name="upload_fail_cancel" msgid="9118496285835687125">"ବନ୍ଦ କରନ୍ତୁ"</string>
-    <string name="bt_error_btn_ok" msgid="5965151173011534240">"ଠିକ୍‌ ଅଛି"</string>
-    <string name="unknown_file" msgid="6092727753965095366">"ଅଜଣା ଫାଇଲ୍‌"</string>
-    <string name="unknown_file_desc" msgid="480434281415453287">"ଏହିଭଳି ଫାଇଲ୍‌କୁ ସମ୍ଭାଳିବା ପାଇଁ କୌଣସି ଆପ୍‌ ନାହିଁ। \n"</string>
-    <string name="not_exist_file" msgid="3489434189599716133">"କୌଣସି ଫାଇଲ୍‌ ନାହିଁ"</string>
-    <string name="not_exist_file_desc" msgid="4059531573790529229">"ଏଭଳି କୌଣସି ଫାଇଲ୍‌ ନାହିଁ। \n"</string>
-    <string name="enabling_progress_title" msgid="436157952334723406">"ଦୟାକରି ଅପେକ୍ଷା କରନ୍ତୁ…"</string>
-    <string name="enabling_progress_content" msgid="4601542238119927904">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ଅନ୍‌ କରୁଛି…"</string>
-    <string name="bt_toast_1" msgid="972182708034353383">"ଫାଇଲ୍‌କୁ ଗ୍ରହଣ କରାଯିବ। ବିଜ୍ଞପ୍ତି ପ୍ୟାନେଲ୍‌ରେ ପ୍ରଗତିକୁ ଦେଖନ୍ତୁ।"</string>
-    <string name="bt_toast_2" msgid="8602553334099066582">"ଫାଇଲ୍‌କୁ ଗ୍ରହଣ କରାଯାଇପାରିବ ନାହିଁ।"</string>
-    <string name="bt_toast_3" msgid="6707884165086862518">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କଠାରୁ ଗ୍ରହଣ କରିବା ବନ୍ଦ ହୋଇଗଲା"</string>
-    <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ଙ୍କୁ ଫାଇଲ୍‌ ପଠାଯାଉଛି"</string>
-    <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"ଙ୍କୁ <xliff:g id="NUMBER">%1$s</xliff:g>ଟି ଫାଇଲ୍‌ ପଠାଯାଉଛି"</string>
-    <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ଙ୍କୁ ଫାଇଲ୍‌ ପଠାଇବା ବନ୍ଦ ହୋଇଗଲା"</string>
-    <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କଠାରୁ ଫାଇଲ୍‌ ସେଭ୍‌ କରିବା ପାଇଁ USB ଷ୍ଟୋରେଜ୍‌ରେ ଯଥେଷ୍ଟ ସ୍ପେସ୍‌ ନାହିଁ"</string>
-    <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କଠାରୁ ଫାଇଲ୍‌ ସେଭ୍‌ କରିବା ପାଇଁ SD କାର୍ଡରେ ଯଥେଷ୍ଟ ସ୍ପେସ୍‌ ନାହିଁ"</string>
-    <string name="bt_sm_2_2" msgid="2965243265852680543">"ସ୍ପେସ୍‌ ଆବଶ୍ୟକ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
-    <string name="ErrorTooManyRequests" msgid="8578277541472944529">"ଅନେକ ଅନୁରୋଧ ଉପରେ କାମ ଚାଲୁଛି। ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
-    <string name="status_pending" msgid="2503691772030877944">"ଏପର୍ଯ୍ୟନ୍ତ ଫାଇଲ୍‌ ଟ୍ରାନ୍ସଫର୍‌ ଆରମ୍ଭ ହୋଇନାହିଁ।"</string>
-    <string name="status_running" msgid="6562808920311008696">"ଫାଇଲ୍‌ ଟ୍ରାନ୍ସଫର୍‌ ଚାଲୁଛି।"</string>
-    <string name="status_success" msgid="239573225847565868">"ଫାଇଲ୍‌ ଟ୍ରାନ୍ସଫର୍‌ ସଫଳତାପୂର୍ବକ ସମ୍ପୂର୍ଣ୍ଣ ହେଲା।"</string>
-    <string name="status_not_accept" msgid="1695082417193780738">"କଣ୍ଟେଣ୍ଟ ସପୋର୍ଟ କରୁନାହିଁ।"</string>
-    <string name="status_forbidden" msgid="613956401054050725">"ଟାର୍ଗେଟ୍‌ ଡିଭାଇସ୍‌ ଦ୍ୱାରା ଟ୍ରାନ୍ସଫର୍‌ ପ୍ରତିବନ୍ଧିତ କରାଯାଇଛି।"</string>
-    <string name="status_canceled" msgid="6664490318773098285">"ୟୁଜର୍‌ଙ୍କ ଦ୍ୱାରା ଟ୍ରାନ୍ସଫର୍‌ କ୍ୟାନ୍ସଲ୍‌ କରାଗଲା।"</string>
-    <string name="status_file_error" msgid="3671917770630165299">"ଷ୍ଟୋରେଜ୍‌ ସମସ୍ୟା।"</string>
-    <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"କୌଣସି USB ଷ୍ଟୋରେଜ୍‌ ନାହିଁ।"</string>
-    <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"କୌଣସି SD କାର୍ଡ ନାହିଁ। ଟ୍ରାନ୍ସଫର୍‌ କରାଯାଇଥିବା ଫାଇଲ୍‌ଗୁଡ଼ିକୁ ସେଭ୍‌ କରିବା ପାଇଁ ଗୋଟିଏ SD କାର୍ଡ ଭର୍ତ୍ତି କରନ୍ତୁ।"</string>
-    <string name="status_connection_error" msgid="947681831523219891">"ସଂଯୋଗ ବିଫଳ ହେଲା।"</string>
-    <string name="status_protocol_error" msgid="3245444473429269539">"ଅନୁରୋଧକୁ ଠିକ୍‌ ଭାବେ ସମ୍ଭାଳି ହେବନାହିଁ।"</string>
-    <string name="status_unknown_error" msgid="8156660554237824912">"ଅଜଣା ତୃଟି।"</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ପ୍ରାପ୍ତ ହେଲା"</string>
-    <string name="opp_notification_group" msgid="3486303082135789982">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ଶେୟାର୍‌"</string>
-    <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> ପ୍ରାପ୍ତ କରିବା ସମ୍ପୂର୍ଣ୍ଣ ହେଲା।"</string>
-    <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> ପଠାଇବା ସମ୍ପୂର୍ଣ୍ଣ ହେଲା।"</string>
-    <string name="inbound_history_title" msgid="6940914942271327563">"ଇନ୍‌ବାଉଣ୍ଡ ଟ୍ରାନ୍ସଫର୍‌"</string>
-    <string name="outbound_history_title" msgid="4279418703178140526">"ଆଉଟ୍‌ବାଉଣ୍ଡ ଟ୍ରାନ୍ସଫର୍‌"</string>
-    <string name="no_transfers" msgid="3482965619151865672">"ଟ୍ରାନ୍ସଫର୍‌ ହିଷ୍ଟୋରୀ ଖାଲି ଅଛି।"</string>
-    <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"ତାଲିକାରୁ ସମସ୍ତ ଆଇଟମ୍‌କୁ ଖାଲି କରିଦିଆଯିବ।"</string>
-    <string name="outbound_noti_title" msgid="8051906709452260849">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ଶେୟାର୍‌: ପଠାଯାଇଥିବା ଫାଇଲ୍‌"</string>
-    <string name="inbound_noti_title" msgid="4143352641953027595">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ଶେୟାର୍‌: ପ୍ରାପ୍ତ କରାଯାଇଥିବା ଫାଇଲ୍‌"</string>
-    <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
-      <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> ବିଫଳ.</item>
-      <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_0">%1$d</xliff:g> ବିଫଳ.</item>
-    </plurals>
-    <plurals name="noti_caption_success" formatted="false" msgid="1572472450257645181">
-      <item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> ସଫଳ, %2$s</item>
-      <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_0">%1$d</xliff:g> ସଫଳ, %2$s</item>
-    </plurals>
-    <string name="transfer_menu_clear_all" msgid="790017462957873132">"ତାଲିକା ଖାଲି କରନ୍ତୁ"</string>
-    <string name="transfer_menu_open" msgid="3368984869083107200">"ଖୋଲନ୍ତୁ"</string>
-    <string name="transfer_menu_clear" msgid="5854038118831427492">"ତାଲିକାରୁ ଖାଲି କରନ୍ତୁ"</string>
-    <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"ଖାଲି କରନ୍ତୁ"</string>
-    <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"ସେଭ୍‌ କରନ୍ତୁ"</string>
-    <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"କ୍ୟାନ୍ସଲ୍‍"</string>
-    <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ମାଧ୍ୟମରେ ଶେୟାର୍‌ କରିବାକୁ ଚାହୁଁଥିବା ଆକାଉଣ୍ଟଗୁଡ଼ିକୁ ଚୟନ କରନ୍ତୁ। ଆପଣଙ୍କୁ ତଥାପି ସଂଯୋଗ କରୁଥିବା ସମୟରେ ଆକାଉଣ୍ଟଗୁଡ଼ିକ ପ୍ରତି ଯେକୌଣସି ଆକ୍ସେସ୍‌କୁ ସ୍ୱୀକାର କରିବାକୁ ପଡ଼ିବ।"</string>
-    <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"ବଳକା ସ୍ଲଟ୍‌:"</string>
-    <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"ଆପ୍ଲିକେଶନ୍‌ ଆଇକନ୍‌"</string>
-    <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ମେସେଜ୍‌ ଶେୟାରିଙ୍ଗ ସେଟିଙ୍ଗ"</string>
-    <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"ଆକାଉଣ୍ଟ ଚୟନ କରାଯାଇପାରିବ ନାହିଁ। 0 ସ୍ଲଟ୍‌ ବଳକା ରହିଲା"</string>
-    <string name="bluetooth_connected" msgid="6718623220072656906">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ଅଡିଓ ସଂଯୁକ୍ତ କରାଗଲା"</string>
-    <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>
-</resources>
diff --git a/res/values-or/strings_pbap.xml b/res/values-or/strings_pbap.xml
deleted file mode 100644
index 804f4a2..0000000
--- a/res/values-or/strings_pbap.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"%1$s ପାଇଁ ସେସନ୍‌ କୀ’ ଟାଇପ୍‌ କରନ୍ତୁ"</string>
-    <string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ସେସନ୍‌ କୀ ଆବଶ୍ୟକ"</string>
-    <string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"%1$s ସହ ଯୋଡ଼ି ହେବାର ସମୟ ସମାପ୍ତ ହୋଇଯାଇଛି"</string>
-    <string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"%1$s ସହ ସେସନ୍‌ କୀ’ ଲେଖିବାର ସମୟ ସମାପ୍ତ ହୋଇଯାଇଛି"</string>
-    <string name="auth_notif_ticker" msgid="1575825798053163744">"Obex ପ୍ରମାଣୀକରଣ ଅନୁରୋଧ"</string>
-    <string name="auth_notif_title" msgid="7599854855681573258">"ସେସନ୍‌ କୀ’"</string>
-    <string name="auth_notif_message" msgid="6667218116427605038">"%1$s ପାଇଁ ସେସନ୍‌ କୀ’ ଟାଇପ୍‌ କରନ୍ତୁ"</string>
-    <string name="defaultname" msgid="4821590500649090078">"କାର୍‌ କିଟ୍‌"</string>
-    <string name="unknownName" msgid="2841414754740600042">"ଅଜଣା ନାମ"</string>
-    <string name="localPhoneName" msgid="2349001318925409159">"ମୋର ନାମ"</string>
-    <string name="defaultnumber" msgid="8520116145890867338">"000000"</string>
-    <string name="pbap_notification_group" msgid="8487669554703627168">"ବ୍ଲୁ-ଟୂଥ୍‍‌ ଯୋଗାଯୋଗ ଶେୟାର୍‌ କରନ୍ତୁ"</string>
-</resources>
diff --git a/res/values-or/strings_pbap_client.xml b/res/values-or/strings_pbap_client.xml
deleted file mode 100644
index 186b23a..0000000
--- a/res/values-or/strings_pbap_client.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
-</resources>
diff --git a/res/values-or/strings_sap.xml b/res/values-or/strings_sap.xml
deleted file mode 100644
index f52d36f..0000000
--- a/res/values-or/strings_sap.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="bluetooth_sap_notif_title" msgid="6877860822993195074">"ବ୍ଲୁ-ଟୂଥ୍‍‌ SIM ଆକ୍ସେସ୍‌"</string>
-    <string name="bluetooth_sap_notif_ticker" msgid="6807778527893726699">"ବ୍ଲୁ-ଟୂଥ୍‍‌ SIM ଆକ୍ସେସ୍‌"</string>
-    <string name="bluetooth_sap_notif_message" msgid="7138657801087500690">"ବିଚ୍ଛିନ୍ନ କରିବା ପାଇଁ କ୍ଲାଏଣ୍ଟଙ୍କୁ ଅନୁରୋଧ କରିବେ?"</string>
-    <string name="bluetooth_sap_notif_disconnecting" msgid="819150843490233288">"ବିଚ୍ଛିନ୍ନ କରିବା ପାଇଁ କ୍ଲାଏଣ୍ଟଙ୍କ ଅପେକ୍ଷା କରାଯାଉଛି"</string>
-    <string name="bluetooth_sap_notif_disconnect_button" msgid="3678476872583356919">"ବିଚ୍ଛିନ୍ନ କରନ୍ତୁ"</string>
-    <string name="bluetooth_sap_notif_force_disconnect_button" msgid="8144086340185532030">"ବିଚ୍ଛିନ୍ନ କରିବା ପାଇଁ ବାଧ୍ୟ କରନ୍ତୁ"</string>
-</resources>
diff --git a/res/values-or/test_strings.xml b/res/values-or/test_strings.xml
deleted file mode 100644
index 693cd65..0000000
--- a/res/values-or/test_strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="6006644116867509664">"ବ୍ଲୁ-ଟୂଥ୍‍‌"</string>
-    <string name="insert_record" msgid="1450997173838378132">"ରେକର୍ଡ ଭର୍ତ୍ତି କରନ୍ତୁ"</string>
-    <string name="update_record" msgid="2480425402384910635">"ରେକର୍ଡ ସୁନିଶ୍ଚିତ କରନ୍ତୁ"</string>
-    <string name="ack_record" msgid="6716152390978472184">"ରେକର୍ଡ ସ୍ୱୀକୃତ କରନ୍ତୁ"</string>
-    <string name="deleteAll_record" msgid="4383349788485210582">"ସମସ୍ତ ରେକର୍ଡ ଡିଲିଟ୍‌ କରନ୍ତୁ"</string>
-    <string name="ok_button" msgid="6519033415223065454">"ଠିକ୍‌ ଅଛି"</string>
-    <string name="delete_record" msgid="4645040331967533724">"ରେକର୍ଡ ଡିଲିଟ୍‌ କରନ୍ତୁ"</string>
-    <string name="start_server" msgid="9034821924409165795">"TCP ସର୍ଭର୍‌ ଆରମ୍ଭ କରନ୍ତୁ"</string>
-    <string name="notify_server" msgid="4369106744022969655">"TCP ସର୍ଭର୍‌କୁ ସୂଚିତ କରନ୍ତୁ"</string>
-</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 0e6a0f1..f0b52bf 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -100,13 +100,13 @@
     <string name="status_connection_error" msgid="947681831523219891">"ਕਨੈਕਸ਼ਨ ਅਸਫਲ।"</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"ਬੇਨਤੀ ਨੂੰ ਸਹੀ ਢੰਗ ਨਾਲ ਸੰਭਾਲਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ।"</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"ਅਗਿਆਤ ਅਸ਼ੁੱਧੀ।"</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"ਬਲੂਟੁੱਥ ਰਾਹੀਂ ਪ੍ਰਾਪਤ"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth ਪ੍ਰਾਪਤ ਕੀਤੀ"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"ਬਲੂਟੁੱਥ ਸਾਂਝਾਕਰਨ"</string>
     <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> ਪੂਰਾ ਪ੍ਰਾਪਤ ਕੀਤਾ।"</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> ਪੂਰਾ ਭੇਜਿਆ ਗਿਆ।"</string>
     <string name="inbound_history_title" msgid="6940914942271327563">"ਇਨਬਾਊਂਡ ਟ੍ਰਾਂਸਫਰ"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"ਆਊਟਬਾਊਂਡ ਟ੍ਰਾਂਸਫਰ"</string>
-    <string name="no_transfers" msgid="3482965619151865672">"ਟ੍ਰਾਂਸਫ਼ਰ ਇਤਿਹਾਸ ਵਿੱਚ ਕੁਝ ਵੀ ਨਹੀਂ ਹੈ।"</string>
+    <string name="no_transfers" msgid="3482965619151865672">"ਟ੍ਰਾਂਸਫਰ ਇਤਿਹਾਸ ਖਾਲੀ ਹੈ।"</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"ਸਾਰੀਆਂ ਆਈਟਮਾਂ ਸੂਚੀ ਵਿੱਚੋਂ ਹਟਾਈਆਂ ਜਾਣਗੀਆਂ।"</string>
     <string name="outbound_noti_title" msgid="8051906709452260849">"Bluetooth ਸ਼ੇਅਰ: ਭੇਜੀਆਂ ਗਈਆਂ ਫਾਈਲਾਂ"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"Bluetooth ਸ਼ੇਅਰ: ਪ੍ਰਾਪਤ ਕੀਤੀਆਂ ਫਾਈਲਾਂ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index ee09650..f1b4575 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -78,7 +78,7 @@
     <string name="not_exist_file_desc" msgid="4059531573790529229">"Plik nie istnieje. \n"</string>
     <string name="enabling_progress_title" msgid="436157952334723406">"Czekaj…"</string>
     <string name="enabling_progress_content" msgid="4601542238119927904">"Włączanie Bluetooth…"</string>
-    <string name="bt_toast_1" msgid="972182708034353383">"Plik zostanie odebrany. Sprawdzaj postęp w panelu powiadomień."</string>
+    <string name="bt_toast_1" msgid="972182708034353383">"Plik zostanie odebrany. Sprawdzaj postęp na panelu powiadomień."</string>
     <string name="bt_toast_2" msgid="8602553334099066582">"Nie można odebrać pliku."</string>
     <string name="bt_toast_3" msgid="6707884165086862518">"Zatrzymano odbiór pliku z urządzenia „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
     <string name="bt_toast_4" msgid="4678812947604395649">"Wysyłanie pliku do urządzenia „<xliff:g id="RECIPIENT">%1$s</xliff:g>”"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index d024a42..9b4d14f 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -124,7 +124,7 @@
     <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Limpar"</string>
     <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Salvar"</string>
     <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancelar"</string>
-    <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecione as contas que você quer compartilhar via Bluetooth. Você ainda precisa aceitar qualquer acesso às contas ao se conectar."</string>
+    <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecione as contas que você deseja compartilhar via Bluetooth. Você ainda precisa aceitar qualquer acesso às contas ao se conectar."</string>
     <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Espaços disponíveis:"</string>
     <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ícone do app"</string>
     <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Configurações de compartilhamento de mensagens Bluetooth"</string>
diff --git a/res/values-si/strings_map.xml b/res/values-si/strings_map.xml
deleted file mode 100644
index 31c2feb..0000000
--- a/res/values-si/strings_map.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="map_session_key_dialog_title" msgid="4357431314165953502">"%1$s සඳහා සැසි යතුර ටයිප් කරන්න"</string>
-    <string name="map_session_key_dialog_header" msgid="1858363785687455516">"බ්ලූටූත් සැසි යතුර අවශ්‍යයි"</string>
-    <string name="map_acceptance_timeout_message" msgid="2247454902690565876">"%1$s හා සම්බන්ධය පිළිගැනීමට කාල නිමාවක්  විය"</string>
-    <string name="map_authentication_timeout_message" msgid="2151423397615327066">"%1$s හා සැසි යතුර ආදානය කිරීමට කාල නිමාවක් විය"</string>
-    <string name="map_auth_notif_ticker" msgid="8840174807927327119">"Obex සත්‍යාපන ඉල්ලීම"</string>
-    <string name="map_auth_notif_title" msgid="301767019364765129">"සැසි යතුර"</string>
-    <string name="map_auth_notif_message" msgid="1510095655064214863">"%1$s සඳහා සැසි යතුර ටයිප් කරන්න"</string>
-    <string name="map_defaultname" msgid="3805234816465216759">"Carkit"</string>
-    <string name="map_unknownName" msgid="4985352365863687360">"නොදන්නා නමකි"</string>
-    <string name="map_localPhoneName" msgid="6264496105941797768">"මගේ නම"</string>
-    <string name="map_defaultnumber" msgid="3838264444043503815">"000000"</string>
-</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 705984a..3631b3a 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -106,7 +106,7 @@
     <string name="upload_success" msgid="4014469387779648949">"Kutuma kwa <xliff:g id="FILE_SIZE">%1$s</xliff:g> kumekamilika."</string>
     <string name="inbound_history_title" msgid="6940914942271327563">"Mahamisho yanayoingia"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"Mahamisho yanayotoka"</string>
-    <string name="no_transfers" msgid="3482965619151865672">"Historia ya uhamishaji haina chochote."</string>
+    <string name="no_transfers" msgid="3482965619151865672">"Historia ya uhamishaji ni tupu."</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"Vipengee vyote vitafutwa kutoka kwenye orodha."</string>
     <string name="outbound_noti_title" msgid="8051906709452260849">"Kushiriki kwa bluetooth: Faili zilizotumwa"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"Kushiriki kwa bluetooth: Faili zilizopokelewa"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 88535c5..4022ba6 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -52,7 +52,7 @@
     <string name="download_line4" msgid="8535996869722666525"></string>
     <string name="download_line5" msgid="3069560415845295386">"Fayl qabul qilinmoqda…"</string>
     <string name="download_cancel" msgid="9177305996747500768">"To‘xtatish"</string>
-    <string name="download_ok" msgid="5000360731674466039">"Berkitish"</string>
+    <string name="download_ok" msgid="5000360731674466039">"Yashirish"</string>
     <string name="incoming_line1" msgid="2127419875681087545">"Kimdan"</string>
     <string name="incoming_line2" msgid="3348994249285315873">"Fayl nomi"</string>
     <string name="incoming_line3" msgid="7954237069667474024">"Hajmi"</string>
@@ -106,7 +106,7 @@
     <string name="upload_success" msgid="4014469387779648949">"To‘liq yuborildi: <xliff:g id="FILE_SIZE">%1$s</xliff:g>"</string>
     <string name="inbound_history_title" msgid="6940914942271327563">"Kiruvchi o‘tkazmalar"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"Chiquvchi o‘tkazmalar"</string>
-    <string name="no_transfers" msgid="3482965619151865672">"Hech narsa topilmadi."</string>
+    <string name="no_transfers" msgid="3482965619151865672">"O‘tkazmalar tarixi bo‘m-bo‘sh."</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"Barcha qaydlar ro‘yxatdan o‘chirib tashlanadi."</string>
     <string name="outbound_noti_title" msgid="8051906709452260849">"Bluetooth orqali yuborildi"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"Bluetooth orqali olindi"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 0be8dea..ea1587a 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -106,7 +106,7 @@
     <string name="upload_success" msgid="4014469387779648949">"Hoàn tất gửi <xliff:g id="FILE_SIZE">%1$s</xliff:g>."</string>
     <string name="inbound_history_title" msgid="6940914942271327563">"Nội dung chuyển đến"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"Nội dung chuyển đi"</string>
-    <string name="no_transfers" msgid="3482965619151865672">"Không có nội dung trong lịch sử truyền."</string>
+    <string name="no_transfers" msgid="3482965619151865672">"Lịch sử chuyển trống."</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"Tất cả các mục sẽ bị xóa khỏi danh sách."</string>
     <string name="outbound_noti_title" msgid="8051906709452260849">"Chia sẻ qua Bluetooth: Tệp đã gửi"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"Chia sẻ qua Bluetooth: Tệp đã nhận"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 4220c84..54bd000 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -106,7 +106,7 @@
     <string name="upload_success" msgid="4014469387779648949">"已完成 <xliff:g id="FILE_SIZE">%1$s</xliff:g> 的傳送作業。"</string>
     <string name="inbound_history_title" msgid="6940914942271327563">"傳入"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"傳出"</string>
-    <string name="no_transfers" msgid="3482965619151865672">"沒有傳輸記錄。"</string>
+    <string name="no_transfers" msgid="3482965619151865672">"傳輸紀錄是空的。"</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"所有項目都會從清單中清除。"</string>
     <string name="outbound_noti_title" msgid="8051906709452260849">"藍牙分享:傳送的檔案"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"藍牙分享:接收的檔案"</string>
diff --git a/res/values-zh-rTW/test_strings.xml b/res/values-zh-rTW/test_strings.xml
index dd2369a..2d2d57b 100644
--- a/res/values-zh-rTW/test_strings.xml
+++ b/res/values-zh-rTW/test_strings.xml
@@ -5,7 +5,7 @@
     <string name="insert_record" msgid="1450997173838378132">"插入記錄"</string>
     <string name="update_record" msgid="2480425402384910635">"確認記錄"</string>
     <string name="ack_record" msgid="6716152390978472184">"Ack 記錄"</string>
-    <string name="deleteAll_record" msgid="4383349788485210582">"刪除所有記錄"</string>
+    <string name="deleteAll_record" msgid="4383349788485210582">"刪除所有紀錄"</string>
     <string name="ok_button" msgid="6519033415223065454">"確定"</string>
     <string name="delete_record" msgid="4645040331967533724">"刪除記錄"</string>
     <string name="start_server" msgid="9034821924409165795">"啟動 TCP 伺服器"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 9301e9d..711993e 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -15,7 +15,6 @@
 <resources>
     <bool name="profile_supported_a2dp">true</bool>
     <bool name="profile_supported_a2dp_sink">false</bool>
-    <bool name="profile_supported_hdp">true</bool>
     <bool name="profile_supported_hs_hfp">true</bool>
     <bool name="profile_supported_hfpclient">false</bool>
     <bool name="profile_supported_hid_host">true</bool>
@@ -32,7 +31,6 @@
     <bool name="profile_supported_pbapclient">false</bool>
     <bool name="profile_supported_mapmce">false</bool>
     <bool name="profile_supported_hid_device">true</bool>
-    <bool name="profile_supported_hearing_aid">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
@@ -71,10 +69,15 @@
 
     <!-- For A2DP sink ducking volume feature. -->
     <integer name="a2dp_sink_duck_percent">25</integer>
+    <!-- If true, device requests audio focus and start avrcp updates on source start or play -->
+    <bool name="a2dp_sink_automatically_request_audio_focus">false</bool>
 
     <!-- For enabling the hfp client connection service -->
     <bool name="hfp_client_connection_service_enabled">false</bool>
 
+    <!-- For supporting emergency call through the hfp client connection service  -->
+    <bool name="hfp_client_connection_service_support_emergency_call">false</bool>
+
     <!-- Enabling autoconnect over pan -->
     <bool name="config_bluetooth_pan_enable_autoconnect">true</bool>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 17023e4..1389e0d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -178,9 +178,9 @@
     <string name="bt_toast_6">Stopped sending file to \u0022<xliff:g id="recipient">%1$s</xliff:g>\u0022</string>
 
     <!-- Bluetooth System Messages [CHAR LIMIT=NONE] -->
-    <string name="bt_sm_2_1" product="nosdcard">There isn\'t enough space in USB storage to save the file from \u0022<xliff:g id="sender">%1$s</xliff:g>\u0022</string>
+    <string name="bt_sm_2_1_nosdcard">There isn\'t enough space in USB storage to save the file from \u0022<xliff:g id="sender">%1$s</xliff:g>\u0022</string>
     <!-- Bluetooth System Messages -->
-    <string name="bt_sm_2_1" product="default">There isn\'t enough space on the SD card to save the file from \u0022<xliff:g id="sender">%1$s</xliff:g>\u0022</string>
+    <string name="bt_sm_2_1_default">There isn\'t enough space on the SD card to save the file from \u0022<xliff:g id="sender">%1$s</xliff:g>\u0022</string>
     <string name="bt_sm_2_2">Space needed: <xliff:g id="size">%1$s</xliff:g></string>
 
     <string name="ErrorTooManyRequests">Too many requests are being processed. Try again later.</string>
@@ -194,8 +194,8 @@
     <string name="status_canceled">Transfer canceled by user.</string>
     <string name="status_file_error">Storage issue.</string>
     <!-- Shown when USB storage cannot be found.  [CHAR LIMIT=NONE] -->
-    <string name="status_no_sd_card" product="nosdcard">No USB storage.</string>
-    <string name="status_no_sd_card" product="default">No SD card. Insert an SD card to save transferred files.</string>
+    <string name="status_no_sd_card_nosdcard">No USB storage.</string>
+    <string name="status_no_sd_card_default">No SD card. Insert an SD card to save transferred files.</string>
     <string name="status_connection_error">Connection unsuccessful.</string>
     <string name="status_protocol_error">Request can\'t be handled correctly.</string>
     <string name="status_unknown_error">Unknown error.</string>
@@ -237,6 +237,7 @@
     <string name="process" translate="false"><xliff:g id="x" /></string>
 
 
+    <string name="bluetooth_a2dp_sink_queue_name">Now Playing</string>
     <string name="bluetooth_map_settings_save">Save</string>
     <string name="bluetooth_map_settings_cancel">Cancel</string>
     <string name="bluetooth_map_settings_intro">Select the accounts you want to share through Bluetooth. You still have to accept any access to the accounts when connecting.</string>
diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java
index 457b053..4432067 100644
--- a/src/com/android/bluetooth/Utils.java
+++ b/src/com/android/bluetooth/Utils.java
@@ -24,6 +24,7 @@
 import android.content.ContextWrapper;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.location.LocationManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.ParcelUuid;
@@ -38,6 +39,8 @@
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
 import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
@@ -109,6 +112,24 @@
         return sb.toString();
     }
 
+    /**
+     * A parser to transfer a byte array to a UTF8 string
+     *
+     * @param valueBuf the byte array to transfer
+     * @return the transferred UTF8 string
+     */
+    public static String byteArrayToUtf8String(byte[] valueBuf) {
+        CharsetDecoder decoder = Charset.forName("UTF8").newDecoder();
+        ByteBuffer byteBuffer = ByteBuffer.wrap(valueBuf);
+        String valueStr = "";
+        try {
+            valueStr = decoder.decode(byteBuffer).toString();
+        } catch (Exception ex) {
+            Log.e(TAG, "Error when parsing byte array to UTF8 String. " + ex);
+        }
+        return valueStr;
+    }
+
     public static byte[] intToByteArray(int value) {
         ByteBuffer converter = ByteBuffer.allocate(4);
         converter.order(ByteOrder.nativeOrder());
@@ -277,73 +298,117 @@
     }
 
     /**
-     * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION or
-     * android.Manifest.permission.ACCESS_FINE_LOCATION and a corresponding app op is allowed
+     * Checks whether location is off and must be on for us to perform some operation
      */
-    public static boolean checkCallerHasLocationPermission(Context context, AppOpsManager appOps,
-            String callingPackage) {
-        if (context.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
-                == PackageManager.PERMISSION_GRANTED && isAppOppAllowed(
-                        appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+    public static boolean blockedByLocationOff(Context context, UserHandle userHandle) {
+        return !context.getSystemService(LocationManager.class)
+                .isLocationEnabledForUser(userHandle);
+    }
+
+    /**
+     * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION and
+     * OP_COARSE_LOCATION is allowed
+     */
+    public static boolean checkCallerHasCoarseLocation(Context context, AppOpsManager appOps,
+            String callingPackage, UserHandle userHandle) {
+        if (blockedByLocationOff(context, userHandle)) {
+            Log.e(TAG, "Permission denial: Location is off.");
+            return false;
+        }
+
+        // Check coarse, but note fine
+        if (context.checkCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_COARSE_LOCATION)
+                        == PackageManager.PERMISSION_GRANTED
+                && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
             return true;
         }
 
-        if (context.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
-                == PackageManager.PERMISSION_GRANTED && isAppOppAllowed(
-                        appOps, AppOpsManager.OP_COARSE_LOCATION, callingPackage)) {
-            return true;
-        }
-        // Enforce location permission for apps targeting M and later versions
-        if (isMApp(context, callingPackage)) {
-            // PEERS_MAC_ADDRESS is another way to get scan results without
-            // requiring location permissions, so only throw an exception here
-            // if PEERS_MAC_ADDRESS permission is missing as well
-            if (!checkCallerHasPeersMacAddressPermission(context)) {
-                throw new SecurityException("Need ACCESS_COARSE_LOCATION or "
-                        + "ACCESS_FINE_LOCATION permission to get scan results");
-            }
-        } else {
-            // Pre-M apps running in the foreground should continue getting scan results
-            if (isForegroundApp(context, callingPackage)) {
-                return true;
-            }
-            Log.e(TAG, "Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION "
-                    + "permission to get scan results");
-        }
+        Log.e(TAG, "Permission denial: Need ACCESS_COARSE_LOCATION "
+                + "permission to get scan results");
         return false;
     }
 
     /**
-     * Returns true if the caller holds PEERS_MAC_ADDRESS.
+     * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION and
+     * OP_COARSE_LOCATION is allowed or android.Manifest.permission.ACCESS_FINE_LOCATION and
+     * OP_FINE_LOCATION is allowed
      */
-    public static boolean checkCallerHasPeersMacAddressPermission(Context context) {
-        return context.checkCallingOrSelfPermission(android.Manifest.permission.PEERS_MAC_ADDRESS)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    public static boolean isLegacyForegroundApp(Context context, String pkgName) {
-        return !isMApp(context, pkgName) && isForegroundApp(context, pkgName);
-    }
-
-    private static boolean isMApp(Context context, String pkgName) {
-        try {
-            return context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion
-                    >= Build.VERSION_CODES.M;
-        } catch (PackageManager.NameNotFoundException e) {
-            // In case of exception, assume M app
+    public static boolean checkCallerHasCoarseOrFineLocation(Context context, AppOpsManager appOps,
+            String callingPackage, UserHandle userHandle) {
+        if (blockedByLocationOff(context, userHandle)) {
+            Log.e(TAG, "Permission denial: Location is off.");
+            return false;
         }
-        return true;
+
+        if (context.checkCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_FINE_LOCATION)
+                        == PackageManager.PERMISSION_GRANTED
+                && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+            return true;
+        }
+
+        // Check coarse, but note fine
+        if (context.checkCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_COARSE_LOCATION)
+                        == PackageManager.PERMISSION_GRANTED
+                && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+            return true;
+        }
+
+        Log.e(TAG, "Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION"
+                + "permission to get scan results");
+        return false;
     }
 
     /**
-     * Return true if the specified package name is a foreground app.
-     *
-     * @param pkgName application package name.
+     * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION and
+     * OP_FINE_LOCATION is allowed
      */
-    private static boolean isForegroundApp(Context context, String pkgName) {
-        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
-        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
-        return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
+    public static boolean checkCallerHasFineLocation(Context context, AppOpsManager appOps,
+            String callingPackage, UserHandle userHandle) {
+        if (blockedByLocationOff(context, userHandle)) {
+            Log.e(TAG, "Permission denial: Location is off.");
+            return false;
+        }
+
+        if (context.checkCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_FINE_LOCATION)
+                        == PackageManager.PERMISSION_GRANTED
+                && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+            return true;
+        }
+
+        Log.e(TAG, "Permission denial: Need ACCESS_FINE_LOCATION "
+                + "permission to get scan results");
+        return false;
+    }
+
+    /**
+     * Returns true if the caller holds NETWORK_SETTINGS
+     */
+    public static boolean checkCallerHasNetworkSettingsPermission(Context context) {
+        return context.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_SETTINGS)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
+     * Returns true if the caller holds NETWORK_SETUP_WIZARD
+     */
+    public static boolean checkCallerHasNetworkSetupWizardPermission(Context context) {
+        return context.checkCallingOrSelfPermission(
+                android.Manifest.permission.NETWORK_SETUP_WIZARD)
+                        == PackageManager.PERMISSION_GRANTED;
+    }
+
+    public static boolean isQApp(Context context, String pkgName) {
+        try {
+            return context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion
+                    >= Build.VERSION_CODES.Q;
+        } catch (PackageManager.NameNotFoundException e) {
+            // In case of exception, assume Q app
+        }
+        return true;
     }
 
     private static boolean isAppOppAllowed(AppOpsManager appOps, int op, String callingPackage) {
diff --git a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
index 4de89df..8127e76 100644
--- a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
+++ b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
@@ -17,13 +17,17 @@
 package com.android.bluetooth.a2dp;
 
 import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
+import android.util.Log;
 
 import com.android.bluetooth.R;
 
+import java.util.Arrays;
+import java.util.Objects;
 /*
  * A2DP Codec Configuration setup.
  */
@@ -52,13 +56,49 @@
     }
 
     void setCodecConfigPreference(BluetoothDevice device,
-                                  BluetoothCodecConfig codecConfig) {
+                                  BluetoothCodecStatus codecStatus,
+                                  BluetoothCodecConfig newCodecConfig) {
+        Objects.requireNonNull(codecStatus);
+
+        // Check whether the codecConfig is selectable for this Bluetooth device.
+        BluetoothCodecConfig[] selectableCodecs = codecStatus.getCodecsSelectableCapabilities();
+        if (!Arrays.asList(selectableCodecs).stream().anyMatch(codec ->
+                codec.isMandatoryCodec())) {
+            // Do not set codec preference to native if the selectableCodecs not contain mandatory
+            // codec. The reason could be remote codec negotiation is not completed yet.
+            Log.w(TAG, "setCodecConfigPreference: must have mandatory codec before changing.");
+            return;
+        }
+        if (!codecStatus.isCodecConfigSelectable(newCodecConfig)) {
+            Log.w(TAG, "setCodecConfigPreference: invalid codec "
+                    + Objects.toString(newCodecConfig));
+            return;
+        }
+
+        // Check whether the codecConfig would change current codec config.
+        int prioritizedCodecType = getPrioitizedCodecType(newCodecConfig, selectableCodecs);
+        BluetoothCodecConfig currentCodecConfig = codecStatus.getCodecConfig();
+        if (prioritizedCodecType == currentCodecConfig.getCodecType()
+                && (prioritizedCodecType != newCodecConfig.getCodecType()
+                || (currentCodecConfig.similarCodecFeedingParameters(newCodecConfig)
+                && currentCodecConfig.sameCodecSpecificParameters(newCodecConfig)))) {
+            // Same codec with same parameters, no need to send this request to native.
+            Log.w(TAG, "setCodecConfigPreference: codec not changed.");
+            return;
+        }
+
         BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
-        codecConfigArray[0] = codecConfig;
+        codecConfigArray[0] = newCodecConfig;
         mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
     }
 
-    void enableOptionalCodecs(BluetoothDevice device) {
+    void enableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
+        if (currentCodecConfig != null && !currentCodecConfig.isMandatoryCodec()) {
+            Log.i(TAG, "enableOptionalCodecs: already using optional codec "
+                    + currentCodecConfig.getCodecName());
+            return;
+        }
+
         BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
         if (codecConfigArray == null) {
             return;
@@ -75,7 +115,12 @@
         mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
     }
 
-    void disableOptionalCodecs(BluetoothDevice device) {
+    void disableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
+        if (currentCodecConfig != null && currentCodecConfig.isMandatoryCodec()) {
+            Log.i(TAG, "disableOptionalCodecs: already using mandatory codec.");
+            return;
+        }
+
         BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
         if (codecConfigArray == null) {
             return;
@@ -92,6 +137,21 @@
         mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
     }
 
+    // Get the codec type of the highest priority of selectableCodecs and codecConfig.
+    private int getPrioitizedCodecType(BluetoothCodecConfig codecConfig,
+            BluetoothCodecConfig[] selectableCodecs) {
+        BluetoothCodecConfig prioritizedCodecConfig = codecConfig;
+        for (BluetoothCodecConfig config : selectableCodecs) {
+            if (prioritizedCodecConfig == null) {
+                prioritizedCodecConfig = config;
+            }
+            if (config.getCodecPriority() > prioritizedCodecConfig.getCodecPriority()) {
+                prioritizedCodecConfig = config;
+            }
+        }
+        return prioritizedCodecConfig.getCodecType();
+    }
+
     // Assign the A2DP Source codec config priorities
     private BluetoothCodecConfig[] assignCodecConfigPriorities() {
         Resources resources = mContext.getResources();
diff --git a/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
index e01b325..cbdc28a 100644
--- a/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
+++ b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
@@ -25,11 +25,11 @@
 import android.bluetooth.BluetoothCodecConfig;
 import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import com.android.bluetooth.Utils;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * A2DP Native Interface to/from JNI.
@@ -107,6 +107,16 @@
     }
 
     /**
+     * Sets a connected A2DP remote device to silence mode.
+     *
+     * @param device the remote device
+     * @return true on success, otherwise false.
+     */
+    public boolean setSilenceDevice(BluetoothDevice device, boolean silence) {
+        return setSilenceDeviceNative(getByteAddress(device), silence);
+    }
+
+    /**
      * Sets a connected A2DP remote device as active.
      *
      * @param device the remote device
@@ -199,6 +209,7 @@
     private native void cleanupNative();
     private native boolean connectA2dpNative(byte[] address);
     private native boolean disconnectA2dpNative(byte[] address);
+    private native boolean setSilenceDeviceNative(byte[] address, boolean silence);
     private native boolean setActiveDeviceNative(byte[] address);
     private native boolean setCodecConfigPreferenceNative(byte[] address,
                 BluetoothCodecConfig[] codecConfigArray);
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 0548cdd..cebf767 100644
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -17,7 +17,6 @@
 package com.android.bluetooth.a2dp;
 
 import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothCodecConfig;
 import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
@@ -30,23 +29,21 @@
 import android.content.IntentFilter;
 import android.media.AudioManager;
 import android.os.HandlerThread;
-import android.provider.Settings;
-import android.support.annotation.GuardedBy;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
+import android.util.StatsLog;
 
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
-import com.android.bluetooth.avrcp.Avrcp;
-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.btservice.ServiceFactory;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
@@ -60,13 +57,13 @@
 
     private static A2dpService sA2dpService;
 
-    private BluetoothAdapter mAdapter;
     private AdapterService mAdapterService;
     private HandlerThread mStateMachinesThread;
-    private Avrcp mAvrcp;
 
     @VisibleForTesting
     A2dpNativeInterface mA2dpNativeInterface;
+    @VisibleForTesting
+    ServiceFactory mFactory = new ServiceFactory();
     private AudioManager mAudioManager;
     private A2dpCodecConfig mA2dpCodecConfig;
 
@@ -102,10 +99,8 @@
             throw new IllegalStateException("start() called twice");
         }
 
-        // Step 1: Get BluetoothAdapter, AdapterService, A2dpNativeInterface, AudioManager.
+        // Step 1: Get AdapterService, A2dpNativeInterface, AudioManager.
         // None of them can be null.
-        mAdapter = Objects.requireNonNull(BluetoothAdapter.getDefaultAdapter(),
-                "BluetoothAdapter cannot be null when A2dpService starts");
         mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
                 "AdapterService cannot be null when A2dpService starts");
         mA2dpNativeInterface = Objects.requireNonNull(A2dpNativeInterface.getInstance(),
@@ -118,28 +113,25 @@
         mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices();
         Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices);
 
-        // Step 3: Setup AVRCP
-        mAvrcp = Avrcp.make(this);
-
-        // Step 4: Start handler thread for state machines
+        // Step 3: Start handler thread for state machines
         mStateMachines.clear();
         mStateMachinesThread = new HandlerThread("A2dpService.StateMachines");
         mStateMachinesThread.start();
 
-        // Step 5: Setup codec config
+        // Step 4: Setup codec config
         mA2dpCodecConfig = new A2dpCodecConfig(this, mA2dpNativeInterface);
 
-        // Step 6: Initialize native interface
+        // Step 5: Initialize native interface
         mA2dpNativeInterface.init(mMaxConnectedAudioDevices,
                                   mA2dpCodecConfig.codecConfigPriorities());
 
-        // Step 7: Check if A2DP is in offload mode
+        // Step 6: Check if A2DP is in offload mode
         mA2dpOffloadEnabled = mAdapterService.isA2dpOffloadEnabled();
         if (DBG) {
             Log.d(TAG, "A2DP offload flag set to " + mA2dpOffloadEnabled);
         }
 
-        // Step 8: Setup broadcast receivers
+        // Step 7: Setup broadcast receivers
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mBondStateChangedReceiver = new BondStateChangedReceiver();
@@ -149,10 +141,10 @@
         mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
         registerReceiver(mConnectionStateChangedReceiver, filter);
 
-        // Step 9: Mark service as started
+        // Step 8: Mark service as started
         setA2dpService(this);
 
-        // Step 10: Clear active device
+        // Step 9: Clear active device
         setActiveDevice(null);
 
         return true;
@@ -166,11 +158,6 @@
             return true;
         }
 
-        // Step 10: Store volume if there is an active device
-        if (mActiveDevice != null && AvrcpTargetService.get() != null) {
-            AvrcpTargetService.get().storeVolumeForDevice(mActiveDevice);
-        }
-
         // Step 9: Clear active device and stop playing audio
         removeActiveDevice(true);
 
@@ -201,19 +188,13 @@
         mStateMachinesThread.quitSafely();
         mStateMachinesThread = null;
 
-        // Step 3: Cleanup AVRCP
-        mAvrcp.doQuit();
-        mAvrcp.cleanup();
-        mAvrcp = null;
-
         // Step 2: Reset maximum number of connected audio devices
         mMaxConnectedAudioDevices = 1;
 
-        // Step 1: Clear BluetoothAdapter, AdapterService, A2dpNativeInterface, AudioManager
+        // Step 1: Clear AdapterService, A2dpNativeInterface, AudioManager
         mAudioManager = null;
         mA2dpNativeInterface = null;
         mAdapterService = null;
-        mAdapter = null;
 
         return true;
     }
@@ -260,8 +241,23 @@
 
         synchronized (mStateMachines) {
             if (!connectionAllowedCheckMaxDevices(device)) {
-                Log.e(TAG, "Cannot connect to " + device + " : too many connected devices");
-                return false;
+                // when mMaxConnectedAudioDevices is one, disconnect current device first.
+                if (mMaxConnectedAudioDevices == 1) {
+                    List<BluetoothDevice> sinks = getDevicesMatchingConnectionStates(
+                            new int[] {BluetoothProfile.STATE_CONNECTED,
+                                    BluetoothProfile.STATE_CONNECTING,
+                                    BluetoothProfile.STATE_DISCONNECTING});
+                    for (BluetoothDevice sink : sinks) {
+                        if (sink.equals(device)) {
+                            Log.w(TAG, "Connecting to device " + device + " : disconnect skipped");
+                            continue;
+                        }
+                        disconnect(sink);
+                    }
+                } else {
+                    Log.e(TAG, "Cannot connect to " + device + " : too many connected devices");
+                    return false;
+                }
             }
             A2dpStateMachine smConnect = getOrCreateStateMachine(device);
             if (smConnect == null) {
@@ -340,7 +336,7 @@
      * request, otherwise is for incoming connection request
      * @return true if connection is allowed, otherwise false
      */
-    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public boolean okToConnect(BluetoothDevice device, boolean isOutgoingRequest) {
         Log.i(TAG, "okToConnect: device " + device + " isOutgoingRequest: " + isOutgoingRequest);
         // Check if this is an incoming connection in Quiet mode.
@@ -375,7 +371,13 @@
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         List<BluetoothDevice> devices = new ArrayList<>();
-        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+        if (states == null) {
+            return devices;
+        }
+        final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+        if (bondedDevices == null) {
+            return devices;
+        }
         synchronized (mStateMachines) {
             for (BluetoothDevice device : bondedDevices) {
                 if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
@@ -387,9 +389,10 @@
                 if (sm != null) {
                     connectionState = sm.getConnectionState();
                 }
-                for (int i = 0; i < states.length; i++) {
-                    if (connectionState == states[i]) {
+                for (int state : states) {
+                    if (connectionState == state) {
                         devices.add(device);
+                        break;
                     }
                 }
             }
@@ -424,14 +427,22 @@
         }
     }
 
+    private void storeActiveDeviceVolume() {
+        // Make sure volume has been stored before been removed from active.
+        if (mFactory.getAvrcpTargetService() != null && mActiveDevice != null) {
+            mFactory.getAvrcpTargetService().storeVolumeForDevice(mActiveDevice);
+        }
+    }
+
     private void removeActiveDevice(boolean forceStopPlayingAudio) {
         BluetoothDevice previousActiveDevice = mActiveDevice;
         synchronized (mStateMachines) {
-            // Clear the active device
-            mActiveDevice = null;
+            // Make sure volume has been store before device been remove from active.
+            storeActiveDeviceVolume();
+
             // This needs to happen before we inform the audio manager that the device
-            // disconnected. Please see comment in broadcastActiveDevice() for why.
-            broadcastActiveDevice(null);
+            // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
+            updateAndBroadcastActiveDevice(null);
 
             if (previousActiveDevice == null) {
                 return;
@@ -458,6 +469,47 @@
     }
 
     /**
+     * Process a change in the silence mode for a {@link BluetoothDevice}.
+     *
+     * @param device the device to change silence mode
+     * @param silence true to enable silence mode, false to disable.
+     * @return true on success, false on error
+     */
+    @VisibleForTesting
+    public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
+        if (DBG) {
+            Log.d(TAG, "setSilenceMode(" + device + "): " + silence);
+        }
+        if (silence && Objects.equals(mActiveDevice, device)) {
+            removeActiveDevice(true);
+        } else if (!silence && mActiveDevice == null) {
+            // Set the device as the active device if currently no active device.
+            setActiveDevice(device);
+        }
+        if (!mA2dpNativeInterface.setSilenceDevice(device, silence)) {
+            Log.e(TAG, "Cannot set " + device + " silence mode " + silence + " in native layer");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Early notification that Hearing Aids will be the active device. This allows the A2DP to save
+     * its volume before the Audio Service starts changing its media stream.
+     */
+    public void earlyNotifyHearingAidActive() {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+
+        synchronized (mStateMachines) {
+            // Switch active device from A2DP to Hearing Aids.
+            if (DBG) {
+                Log.d(TAG, "earlyNotifyHearingAidActive: Save volume for " + mActiveDevice);
+            }
+            storeActiveDeviceVolume();
+        }
+    }
+
+    /**
      * Set the active device.
      *
      * @param device the active device
@@ -471,10 +523,6 @@
                 Log.d(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice);
             }
 
-            if (previousActiveDevice != null && AvrcpTargetService.get() != null) {
-                AvrcpTargetService.get().storeVolumeForDevice(previousActiveDevice);
-            }
-
             if (device == null) {
                 // Remove active device and continue playing audio only if necessary.
                 removeActiveDevice(false);
@@ -500,10 +548,17 @@
             codecStatus = sm.getCodecStatus();
 
             boolean deviceChanged = !Objects.equals(device, mActiveDevice);
-            mActiveDevice = device;
+            if (deviceChanged) {
+                // Switch from one A2DP to another A2DP device
+                if (DBG) {
+                    Log.d(TAG, "Switch A2DP devices to " + device + " from " + mActiveDevice);
+                }
+                storeActiveDeviceVolume();
+            }
+
             // This needs to happen before we inform the audio manager that the device
-            // disconnected. Please see comment in broadcastActiveDevice() for why.
-            broadcastActiveDevice(mActiveDevice);
+            // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
+            updateAndBroadcastActiveDevice(device);
             if (deviceChanged) {
                 // Send an intent with the active device codec config
                 if (codecStatus != null) {
@@ -516,7 +571,7 @@
                 if (previousActiveDevice != null) {
                     if (!mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)) {
                         mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
-                                                         AudioManager.ADJUST_MUTE, 0);
+                                AudioManager.ADJUST_MUTE, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
                         wasMuted = true;
                     }
                     mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
@@ -525,10 +580,8 @@
                 }
 
                 int rememberedVolume = -1;
-                if (AvrcpTargetService.get() != null) {
-                    AvrcpTargetService.get().volumeDeviceSwitched(mActiveDevice);
-
-                    rememberedVolume = AvrcpTargetService.get()
+                if (mFactory.getAvrcpTargetService() != null) {
+                    rememberedVolume = mFactory.getAvrcpTargetService()
                             .getRememberedVolumeForDevice(mActiveDevice);
                 }
 
@@ -542,7 +595,7 @@
                 mAudioManager.handleBluetoothA2dpDeviceConfigChange(mActiveDevice);
                 if (wasMuted) {
                     mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
-                                                     AudioManager.ADJUST_UNMUTE, 0);
+                                AudioManager.ADJUST_UNMUTE, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
                 }
             }
         }
@@ -569,46 +622,33 @@
 
     public boolean setPriority(BluetoothDevice device, int priority) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
         if (DBG) {
             Log.d(TAG, "Saved priority " + device + " = " + priority);
         }
+        mAdapterService.getDatabase()
+                .setProfilePriority(device, BluetoothProfile.A2DP, priority);
         return true;
     }
 
     public int getPriority(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        int priority = Settings.Global.getInt(getContentResolver(),
-                Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
-                BluetoothProfile.PRIORITY_UNDEFINED);
-        return priority;
+        return mAdapterService.getDatabase()
+                .getProfilePriority(device, BluetoothProfile.A2DP);
     }
 
-    /* Absolute volume implementation */
     public boolean isAvrcpAbsoluteVolumeSupported() {
-        return mAvrcp.isAbsoluteVolumeSupported();
+        // TODO (apanicke): Add a hook here for the AvrcpTargetService.
+        return false;
     }
 
+
     public void setAvrcpAbsoluteVolume(int volume) {
         // TODO (apanicke): Instead of using A2DP as a middleman for volume changes, add a binder
         // service to the new AVRCP Profile and have the audio manager use that instead.
-        if (AvrcpTargetService.get() != null) {
-            AvrcpTargetService.get().sendVolumeChanged(volume);
+        if (mFactory.getAvrcpTargetService() != null) {
+            mFactory.getAvrcpTargetService().sendVolumeChanged(volume);
             return;
         }
-
-        mAvrcp.setAbsoluteVolume(volume);
-    }
-
-    public void setAvrcpAudioState(int state) {
-        mAvrcp.setA2dpAudioState(state);
-    }
-
-    public void resetAvrcpBlacklist(BluetoothDevice device) {
-        if (mAvrcp != null) {
-            mAvrcp.resetBlackList(device.getAddress());
-        }
     }
 
     boolean isA2dpPlaying(BluetoothDevice device) {
@@ -672,10 +712,19 @@
             device = mActiveDevice;
         }
         if (device == null) {
-            Log.e(TAG, "Cannot set codec config preference: no active A2DP device");
+            Log.e(TAG, "setCodecConfigPreference: Invalid device");
             return;
         }
-        mA2dpCodecConfig.setCodecConfigPreference(device, codecConfig);
+        if (codecConfig == null) {
+            Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
+            return;
+        }
+        BluetoothCodecStatus codecStatus = getCodecStatus(device);
+        if (codecStatus == null) {
+            Log.e(TAG, "setCodecConfigPreference: Codec status is null");
+            return;
+        }
+        mA2dpCodecConfig.setCodecConfigPreference(device, codecStatus, codecConfig);
     }
 
     /**
@@ -694,10 +743,19 @@
             device = mActiveDevice;
         }
         if (device == null) {
-            Log.e(TAG, "Cannot enable optional codecs: no active A2DP device");
+            Log.e(TAG, "enableOptionalCodecs: Invalid device");
             return;
         }
-        mA2dpCodecConfig.enableOptionalCodecs(device);
+        if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+            Log.e(TAG, "enableOptionalCodecs: No optional codecs");
+            return;
+        }
+        BluetoothCodecStatus codecStatus = getCodecStatus(device);
+        if (codecStatus == null) {
+            Log.e(TAG, "enableOptionalCodecs: Codec status is null");
+            return;
+        }
+        mA2dpCodecConfig.enableOptionalCodecs(device, codecStatus.getCodecConfig());
     }
 
     /**
@@ -716,34 +774,36 @@
             device = mActiveDevice;
         }
         if (device == null) {
-            Log.e(TAG, "Cannot disable optional codecs: no active A2DP device");
+            Log.e(TAG, "disableOptionalCodecs: Invalid device");
             return;
         }
-        mA2dpCodecConfig.disableOptionalCodecs(device);
+        if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+            Log.e(TAG, "disableOptionalCodecs: No optional codecs");
+            return;
+        }
+        BluetoothCodecStatus codecStatus = getCodecStatus(device);
+        if (codecStatus == null) {
+            Log.e(TAG, "disableOptionalCodecs: Codec status is null");
+            return;
+        }
+        mA2dpCodecConfig.disableOptionalCodecs(device, codecStatus.getCodecConfig());
     }
 
     public int getSupportsOptionalCodecs(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        int support = Settings.Global.getInt(getContentResolver(),
-                Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
-                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
-        return support;
+        return mAdapterService.getDatabase().getA2dpSupportsOptionalCodecs(device);
     }
 
     public void setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         int value = doesSupport ? BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED
                 : BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED;
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
-                value);
+        mAdapterService.getDatabase().setA2dpSupportsOptionalCodecs(device, value);
     }
 
     public int getOptionalCodecsEnabled(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        return Settings.Global.getInt(getContentResolver(),
-                Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()),
-                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+        return mAdapterService.getDatabase().getA2dpOptionalCodecsEnabled(device);
     }
 
     public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
@@ -754,9 +814,7 @@
             Log.w(TAG, "Unexpected value passed to setOptionalCodecsEnabled:" + value);
             return;
         }
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()),
-                value);
+        mAdapterService.getDatabase().setA2dpOptionalCodecsEnabled(device, value);
     }
 
     // Handle messages from native (JNI) to Java
@@ -800,8 +858,27 @@
      * @param sameAudioFeedingParameters if true the audio feeding parameters
      * haven't been changed
      */
-    void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
+    @VisibleForTesting
+    public void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
                             boolean sameAudioFeedingParameters) {
+        // Log codec config and capability metrics
+        BluetoothCodecConfig codecConfig = codecStatus.getCodecConfig();
+        StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CONFIG_CHANGED,
+                mAdapterService.obfuscateAddress(device), codecConfig.getCodecType(),
+                codecConfig.getCodecPriority(), codecConfig.getSampleRate(),
+                codecConfig.getBitsPerSample(), codecConfig.getChannelMode(),
+                codecConfig.getCodecSpecific1(), codecConfig.getCodecSpecific2(),
+                codecConfig.getCodecSpecific3(), codecConfig.getCodecSpecific4());
+        BluetoothCodecConfig[] codecCapabilities = codecStatus.getCodecsSelectableCapabilities();
+        for (BluetoothCodecConfig codecCapability : codecCapabilities) {
+            StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CAPABILITY_CHANGED,
+                    mAdapterService.obfuscateAddress(device), codecCapability.getCodecType(),
+                    codecCapability.getCodecPriority(), codecCapability.getSampleRate(),
+                    codecCapability.getBitsPerSample(), codecCapability.getChannelMode(),
+                    codecConfig.getCodecSpecific1(), codecConfig.getCodecSpecific2(),
+                    codecConfig.getCodecSpecific3(), codecConfig.getCodecSpecific4());
+        }
+
         broadcastCodecConfig(device, codecStatus);
 
         // Inform the Audio Service about the codec configuration change,
@@ -838,11 +915,24 @@
         }
     }
 
-    private void broadcastActiveDevice(BluetoothDevice device) {
+    // This needs to run before any of the Audio Manager connection functions since
+    // AVRCP needs to be aware that the audio device is changed before the Audio Manager
+    // changes the volume of the output devices.
+    private void updateAndBroadcastActiveDevice(BluetoothDevice device) {
         if (DBG) {
-            Log.d(TAG, "broadcastActiveDevice(" + device + ")");
+            Log.d(TAG, "updateAndBroadcastActiveDevice(" + device + ")");
         }
 
+        synchronized (mStateMachines) {
+            if (mFactory.getAvrcpTargetService() != null) {
+                mFactory.getAvrcpTargetService().volumeDeviceSwitched(device);
+            }
+
+            mActiveDevice = device;
+        }
+
+        StatsLog.write(StatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.A2DP,
+                mAdapterService.obfuscateAddress(device));
         Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
@@ -902,6 +992,10 @@
             if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
                 return;
             }
+            if (mFactory.getAvrcpTargetService() != null) {
+                mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
+            }
+
             removeStateMachine(device);
         }
     }
@@ -921,9 +1015,17 @@
         }
     }
 
-    private void updateOptionalCodecsSupport(BluetoothDevice device) {
+
+    /**
+     * Update and initiate optional codec status change to native.
+     *
+     * @param device the device to change optional codec status
+     */
+    @VisibleForTesting
+    public void updateOptionalCodecsSupport(BluetoothDevice device) {
         int previousSupport = getSupportsOptionalCodecs(device);
         boolean supportsOptional = false;
+        boolean hasMandatoryCodec = false;
 
         synchronized (mStateMachines) {
             A2dpStateMachine sm = mStateMachines.get(device);
@@ -933,13 +1035,22 @@
             BluetoothCodecStatus codecStatus = sm.getCodecStatus();
             if (codecStatus != null) {
                 for (BluetoothCodecConfig config : codecStatus.getCodecsSelectableCapabilities()) {
-                    if (!config.isMandatoryCodec()) {
+                    if (config.isMandatoryCodec()) {
+                        hasMandatoryCodec = true;
+                    } else {
                         supportsOptional = true;
-                        break;
                     }
                 }
             }
         }
+        if (!hasMandatoryCodec) {
+            // Mandatory codec(SBC) is not selectable. It could be caused by the remote device
+            // select codec before native finish get codec capabilities. Stop use this codec
+            // status as the reference to support/enable optional codecs.
+            Log.i(TAG, "updateOptionalCodecsSupport: Mandatory codec is not selectable.");
+            return;
+        }
+
         if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
                 || supportsOptional != (previousSupport
                                     == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) {
@@ -947,10 +1058,17 @@
         }
         if (supportsOptional) {
             int enabled = getOptionalCodecsEnabled(device);
-            if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
-                enableOptionalCodecs(device);
-            } else if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED) {
-                disableOptionalCodecs(device);
+            switch (enabled) {
+                case BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN:
+                    // Enable optional codec by default.
+                    setOptionalCodecsEnabled(device, BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
+                    // Fall through intended
+                case BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED:
+                    enableOptionalCodecs(device);
+                    break;
+                case BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED:
+                    disableOptionalCodecs(device);
+                    break;
             }
         }
     }
@@ -961,10 +1079,6 @@
         }
         synchronized (mStateMachines) {
             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
@@ -979,6 +1093,10 @@
             if (toState == BluetoothProfile.STATE_DISCONNECTED) {
                 int bondState = mAdapterService.getBondState(device);
                 if (bondState == BluetoothDevice.BOND_NONE) {
+                    if (mFactory.getAvrcpTargetService() != null) {
+                        mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
+                    }
+
                     removeStateMachine(device);
                 }
             }
@@ -1212,8 +1330,5 @@
         for (A2dpStateMachine sm : mStateMachines.values()) {
             sm.dump(sb);
         }
-        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..e0df8b2 100644
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -53,10 +53,11 @@
 import android.content.Intent;
 import android.os.Looper;
 import android.os.Message;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
+import android.util.StatsLog;
 
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
@@ -88,7 +89,8 @@
 
     private A2dpService mA2dpService;
     private A2dpNativeInterface mA2dpNativeInterface;
-    private boolean mA2dpOffloadEnabled = false;
+    @VisibleForTesting
+    boolean mA2dpOffloadEnabled = false;
     private final BluetoothDevice mDevice;
     private boolean mIsPlaying = false;
     private BluetoothCodecStatus mCodecStatus;
@@ -130,7 +132,6 @@
             // Stop if auido is still playing
             log("doQuit: stopped playing " + mDevice);
             mIsPlaying = false;
-            mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
             broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
                                 BluetoothA2dp.STATE_PLAYING);
         }
@@ -158,7 +159,6 @@
                 if (mIsPlaying) {
                     Log.i(TAG, "Disconnected: stopped playing: " + mDevice);
                     mIsPlaying = false;
-                    mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
                     broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
                                         BluetoothA2dp.STATE_PLAYING);
                 }
@@ -468,6 +468,10 @@
 
             removeDeferredMessages(CONNECT);
 
+            // 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.
+            mA2dpService.updateOptionalCodecsSupport(mDevice);
             broadcastConnectionState(mConnectionState, mLastConnectionState);
             // Upon connected, the audio starts out as stopped
             broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
@@ -559,7 +563,6 @@
                         if (!mIsPlaying) {
                             Log.i(TAG, "Connected: started playing: " + mDevice);
                             mIsPlaying = true;
-                            mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
                             broadcastAudioState(BluetoothA2dp.STATE_PLAYING,
                                                 BluetoothA2dp.STATE_NOT_PLAYING);
                         }
@@ -571,7 +574,6 @@
                         if (mIsPlaying) {
                             Log.i(TAG, "Connected: stopped playing: " + mDevice);
                             mIsPlaying = false;
-                            mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
                             broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
                                                 BluetoothA2dp.STATE_PLAYING);
                         }
@@ -611,8 +613,11 @@
     }
 
     // NOTE: This event is processed in any state
-    private void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
+    @VisibleForTesting
+    void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
         BluetoothCodecConfig prevCodecConfig = null;
+        BluetoothCodecStatus prevCodecStatus = mCodecStatus;
+
         synchronized (this) {
             if (mCodecStatus != null) {
                 prevCodecConfig = mCodecStatus.getCodecConfig();
@@ -633,6 +638,12 @@
             }
         }
 
+        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.
+            mA2dpService.updateOptionalCodecsSupport(mDevice);
+        }
         if (mA2dpOffloadEnabled) {
             boolean update = false;
             BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
@@ -676,7 +687,7 @@
     private void broadcastAudioState(int newState, int prevState) {
         log("A2DP Playing state : device: " + mDevice + " State:" + audioStateToString(prevState)
                 + "->" + audioStateToString(newState));
-
+        StatsLog.write(StatsLog.BLUETOOTH_A2DP_PLAYBACK_STATE_CHANGED, newState);
         Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
@@ -699,6 +710,16 @@
         return builder.toString();
     }
 
+    private static boolean sameSelectableCodec(BluetoothCodecStatus prevCodecStatus,
+            BluetoothCodecStatus newCodecStatus) {
+        if (prevCodecStatus == null) {
+            return false;
+        }
+        return BluetoothCodecStatus.sameCapabilities(
+                prevCodecStatus.getCodecsSelectableCapabilities(),
+                newCodecStatus.getCodecsSelectableCapabilities());
+    }
+
     private static String messageWhatToString(int what) {
         switch (what) {
             case CONNECT:
@@ -752,7 +773,6 @@
                 ProfileService.println(sb, "  mCodecConfig: " + mCodecStatus.getCodecConfig());
             }
         }
-        ProfileService.println(sb, "  StateMachine: " + this);
         // Dump the state machine logs
         StringWriter stringWriter = new StringWriter();
         PrintWriter printWriter = new PrintWriter(stringWriter);
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index 17c8885..e41def0 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -13,224 +13,94 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.bluetooth.a2dpsink;
 
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAudioConfig;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothA2dpSink;
-import android.content.Intent;
-import android.provider.Settings;
 import android.util.Log;
 
 import com.android.bluetooth.Utils;
-import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService;
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application.
  * @hide
  */
 public class A2dpSinkService extends ProfileService {
-    private static final boolean DBG = true;
     private static final String TAG = "A2dpSinkService";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+    static final int MAXIMUM_CONNECTED_DEVICES = 1;
 
-    private A2dpSinkStateMachine mStateMachine;
-    private static A2dpSinkService sA2dpSinkService;
+    private final BluetoothAdapter mAdapter;
+    protected Map<BluetoothDevice, A2dpSinkStateMachine> mDeviceStateMap =
+            new ConcurrentHashMap<>(1);
 
-    @Override
-    protected IProfileServiceBinder initBinder() {
-        return new BluetoothA2dpSinkBinder(this);
+    private A2dpSinkStreamHandler mA2dpSinkStreamHandler;
+    private static A2dpSinkService sService;
+
+    static {
+        classInitNative();
     }
 
     @Override
     protected boolean start() {
-        if (DBG) {
-            Log.d(TAG, "start()");
-        }
-        // Start the media browser service.
-        Intent startIntent = new Intent(this, A2dpMediaBrowserService.class);
-        startService(startIntent);
-        mStateMachine = A2dpSinkStateMachine.make(this, this);
-        setA2dpSinkService(this);
+        initNative();
+        sService = this;
+        mA2dpSinkStreamHandler = new A2dpSinkStreamHandler(this, this);
         return true;
     }
 
     @Override
     protected boolean stop() {
-        if (DBG) {
-            Log.d(TAG, "stop()");
+        for (A2dpSinkStateMachine stateMachine : mDeviceStateMap.values()) {
+            stateMachine.quitNow();
         }
-        setA2dpSinkService(null);
-        if (mStateMachine != null) {
-            mStateMachine.doQuit();
-        }
-        Intent stopIntent = new Intent(this, A2dpMediaBrowserService.class);
-        stopService(stopIntent);
+        sService = null;
         return true;
     }
 
+    public static A2dpSinkService getA2dpSinkService() {
+        return sService;
+    }
+
+    public A2dpSinkService() {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+    }
+
+    protected A2dpSinkStateMachine newStateMachine(BluetoothDevice device) {
+        return new A2dpSinkStateMachine(device, this);
+    }
+
+    protected synchronized A2dpSinkStateMachine getStateMachine(BluetoothDevice device) {
+        return mDeviceStateMap.get(device);
+    }
+
+    /**
+     * Request audio focus such that the designated device can stream audio
+     */
+    public void requestAudioFocus(BluetoothDevice device, boolean request) {
+        mA2dpSinkStreamHandler.requestAudioFocus(request);
+    }
+
     @Override
-    protected void cleanup() {
-        if (mStateMachine != null) {
-            mStateMachine.cleanup();
-        }
-    }
-
-    //API Methods
-
-    public static synchronized A2dpSinkService getA2dpSinkService() {
-        if (sA2dpSinkService == null) {
-            Log.w(TAG, "getA2dpSinkService(): service is null");
-            return null;
-        }
-        if (!sA2dpSinkService.isAvailable()) {
-            Log.w(TAG, "getA2dpSinkService(): service is not available ");
-            return null;
-        }
-        return sA2dpSinkService;
-    }
-
-    private static synchronized void setA2dpSinkService(A2dpSinkService instance) {
-        if (DBG) {
-            Log.d(TAG, "setA2dpSinkService(): set to: " + instance);
-        }
-        sA2dpSinkService = instance;
-    }
-
-    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 (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) {
-            return false;
-        }
-
-        mStateMachine.sendMessage(A2dpSinkStateMachine.DISCONNECT, device);
-        return true;
-    }
-
-    public List<BluetoothDevice> getConnectedDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getConnectedDevices();
-    }
-
-    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getDevicesMatchingConnectionStates(states);
-    }
-
-    int getConnectionState(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getConnectionState(device);
-    }
-
-    public boolean setPriority(BluetoothDevice device, int priority) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()), priority);
-        if (DBG) {
-            Log.d(TAG, "Saved priority " + device + " = " + priority);
-        }
-        return true;
-    }
-
-    public int getPriority(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        int priority = Settings.Global.getInt(getContentResolver(),
-                Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
-                BluetoothProfile.PRIORITY_UNDEFINED);
-        return priority;
-    }
-
-    /**
-     * Called by AVRCP controller to provide information about the last user intent on CT.
-     *
-     * If the user has pressed play in the last attempt then A2DP Sink component will grant focus to
-     * any incoming sound from the phone (and also retain focus for a few seconds before
-     * relinquishing. On the other hand if the user has pressed pause/stop then the A2DP sink
-     * 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) {
-        if (mStateMachine != null) {
-            if (keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PLAY
-                    && keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
-                mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PLAY);
-            } else if ((keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE
-                    || keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_STOP)
-                    && keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
-                mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PAUSE);
-            }
-        }
-    }
-
-    /**
-     * Called by AVRCP controller to provide information about the last user intent on TG.
-     *
-     * Tf the user has pressed pause on the TG then we can preempt streaming music. This is opposed
-     * to when the streaming stops abruptly (jitter) in which case we will wait for sometime before
-     * stopping playback.
-     */
-    public void informTGStatePlaying(BluetoothDevice device, boolean isPlaying) {
-        if (mStateMachine != null) {
-            if (!isPlaying) {
-                mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PAUSE);
-            } else {
-                mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PLAY);
-            }
-        }
-    }
-
-    /**
-     * Called by AVRCP controller to establish audio focus.
-     *
-     * In order to perform streaming the A2DP sink must have audio focus.  This interface allows the
-     * associated MediaSession to inform the sink of intent to play and then allows streaming to be
-     * 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);
-        }
-    }
-
-    synchronized boolean isA2dpPlaying(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (DBG) {
-            Log.d(TAG, "isA2dpPlaying(" + device + ")");
-        }
-        return mStateMachine.isPlaying(device);
-    }
-
-    BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getAudioConfig(device);
+    protected IProfileServiceBinder initBinder() {
+        return new A2dpSinkServiceBinder(this);
     }
 
     //Binder object: Must be static class or memory leak may occur
-    private static class BluetoothA2dpSinkBinder extends IBluetoothA2dpSink.Stub
+    private static class A2dpSinkServiceBinder extends IBluetoothA2dpSink.Stub
             implements IProfileServiceBinder {
         private A2dpSinkService mService;
 
@@ -240,13 +110,13 @@
                 return null;
             }
 
-            if (mService != null && mService.isAvailable()) {
+            if (mService != null) {
                 return mService;
             }
             return null;
         }
 
-        BluetoothA2dpSinkBinder(A2dpSinkService svc) {
+        A2dpSinkServiceBinder(A2dpSinkService svc) {
             mService = svc;
         }
 
@@ -301,15 +171,6 @@
         }
 
         @Override
-        public boolean isA2dpPlaying(BluetoothDevice device) {
-            A2dpSinkService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.isA2dpPlaying(device);
-        }
-
-        @Override
         public boolean setPriority(BluetoothDevice device, int priority) {
             A2dpSinkService service = getService();
             if (service == null) {
@@ -328,6 +189,15 @@
         }
 
         @Override
+        public boolean isA2dpPlaying(BluetoothDevice device) {
+            A2dpSinkService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.isA2dpPlaying(device);
+        }
+
+        @Override
         public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
             A2dpSinkService service = getService();
             if (service == null) {
@@ -337,13 +207,222 @@
         }
     }
 
-    ;
+    /* Generic Profile Code */
+
+    /**
+     * Connect the given Bluetooth device.
+     *
+     * @return true if connection is successful, false otherwise.
+     */
+    public synchronized boolean connect(BluetoothDevice device) {
+        if (device == null) {
+            throw new IllegalArgumentException("Null device");
+        }
+        if (DBG) {
+            StringBuilder sb = new StringBuilder();
+            dump(sb);
+            Log.d(TAG, " connect device: " + device
+                    + ", InstanceMap start state: " + sb.toString());
+        }
+        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
+            Log.w(TAG, "Connection not allowed: <" + device.getAddress() + "> is PRIORITY_OFF");
+            return false;
+        }
+        A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(device);
+        if (stateMachine != null) {
+            stateMachine.connect();
+            return true;
+        } else {
+            // a state machine instance doesn't exist yet, and the max has been reached.
+            Log.e(TAG, "Maxed out on the number of allowed MAP connections. "
+                    + "Connect request rejected on " + device);
+            return false;
+        }
+    }
+
+    /**
+     * Disconnect the given Bluetooth device.
+     *
+     * @return true if disconnect is successful, false otherwise.
+     */
+    public synchronized boolean disconnect(BluetoothDevice device) {
+        if (DBG) {
+            StringBuilder sb = new StringBuilder();
+            dump(sb);
+            Log.d(TAG, "A2DP disconnect device: " + device
+                    + ", InstanceMap start state: " + sb.toString());
+        }
+        A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
+        // a state machine instance doesn't exist. maybe it is already gone?
+        if (stateMachine == null) {
+            return false;
+        }
+        int connectionState = stateMachine.getState();
+        if (connectionState == BluetoothProfile.STATE_DISCONNECTED
+                || connectionState == BluetoothProfile.STATE_DISCONNECTING) {
+            return false;
+        }
+        // upon completion of disconnect, the state machine will remove itself from the available
+        // devices map
+        stateMachine.disconnect();
+        return true;
+    }
+
+    void removeStateMachine(A2dpSinkStateMachine stateMachine) {
+        mDeviceStateMap.remove(stateMachine.getDevice());
+    }
+
+    public List<BluetoothDevice> getConnectedDevices() {
+        return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
+    }
+
+    protected A2dpSinkStateMachine getOrCreateStateMachine(BluetoothDevice device) {
+        A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
+        if (stateMachine == null) {
+            stateMachine = newStateMachine(device);
+            mDeviceStateMap.put(device, stateMachine);
+            stateMachine.start();
+        }
+        return stateMachine;
+    }
+
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
+        List<BluetoothDevice> deviceList = new ArrayList<>();
+        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+        int connectionState;
+        for (BluetoothDevice device : bondedDevices) {
+            connectionState = getConnectionState(device);
+            if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState);
+            for (int i = 0; i < states.length; i++) {
+                if (connectionState == states[i]) {
+                    deviceList.add(device);
+                }
+            }
+        }
+        if (DBG) Log.d(TAG, deviceList.toString());
+        Log.d(TAG, "GetDevicesDone");
+        return deviceList;
+    }
+
+    synchronized int getConnectionState(BluetoothDevice device) {
+        A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
+        return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED
+                : stateMachine.getState();
+    }
+
+    /**
+     * Set the priority of the  profile.
+     *
+     * @param device   the remote device
+     * @param priority the priority of the profile
+     * @return true on success, otherwise false
+     */
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "Saved priority " + device + " = " + priority);
+        }
+        AdapterService.getAdapterService().getDatabase()
+                .setProfilePriority(device, BluetoothProfile.A2DP_SINK, priority);
+        return true;
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * @param device the remote device
+     * @return priority of the specified device
+     */
+    public int getPriority(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        return AdapterService.getAdapterService().getDatabase()
+                .getProfilePriority(device, BluetoothProfile.A2DP_SINK);
+    }
+
 
     @Override
     public void dump(StringBuilder sb) {
         super.dump(sb);
-        if (mStateMachine != null) {
-            mStateMachine.dump(sb);
+        ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size());
+        for (A2dpSinkStateMachine stateMachine : mDeviceStateMap.values()) {
+            ProfileService.println(sb,
+                    "==== StateMachine for " + stateMachine.getDevice() + " ====");
+            stateMachine.dump(sb);
         }
     }
+
+    /**
+     * Get the current Bluetooth Audio focus state
+     *
+     * @return focus
+     */
+    public static int getFocusState() {
+        return sService.mA2dpSinkStreamHandler.getFocusState();
+    }
+
+    boolean isA2dpPlaying(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mA2dpSinkStreamHandler.isPlaying();
+    }
+
+    BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
+        A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
+        // a state machine instance doesn't exist. maybe it is already gone?
+        if (stateMachine == null) {
+            return null;
+        }
+        return stateMachine.getAudioConfig();
+    }
+
+    /* JNI interfaces*/
+
+    private static native void classInitNative();
+
+    private native void initNative();
+
+    private native void cleanupNative();
+
+    native boolean connectA2dpNative(byte[] address);
+
+    native boolean disconnectA2dpNative(byte[] address);
+
+    /**
+     * inform A2DP decoder of the current audio focus
+     *
+     * @param focusGranted
+     */
+    @VisibleForTesting
+    public native void informAudioFocusStateNative(int focusGranted);
+
+    /**
+     * inform A2DP decoder the desired audio gain
+     *
+     * @param gain
+     */
+    @VisibleForTesting
+    public native void informAudioTrackGainNative(float gain);
+
+    private void onConnectionStateChanged(byte[] address, int state) {
+        StackEvent event = StackEvent.connectionStateChanged(getDevice(address), state);
+        A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice);
+        stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+    }
+
+    private void onAudioStateChanged(byte[] address, int state) {
+        if (state == StackEvent.AUDIO_STATE_STARTED) {
+            mA2dpSinkStreamHandler.obtainMessage(
+                    A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
+        } else if (state == StackEvent.AUDIO_STATE_STOPPED) {
+            mA2dpSinkStreamHandler.obtainMessage(
+                    A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
+        }
+    }
+
+    private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
+        StackEvent event = StackEvent.audioConfigChanged(getDevice(address), sampleRate,
+                channelCount);
+        A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice);
+        stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+    }
 }
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index fb64318..19ed87f 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -13,874 +13,288 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-/**
- * Bluetooth A2dp Sink StateMachine
- *                      (Disconnected)
- *                           |    ^
- *                   CONNECT |    | DISCONNECTED
- *                           V    |
- *                         (Pending)
- *                           |    ^
- *                 CONNECTED |    | CONNECT
- *                           V    |
- *                        (Connected -- See A2dpSinkStreamHandler)
- */
 package com.android.bluetooth.a2dpsink;
 
 import android.bluetooth.BluetoothA2dpSink;
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAudioConfig;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
-import android.content.Context;
 import android.content.Intent;
 import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.os.Handler;
 import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.PowerManager;
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
-import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
-import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Set;
 
 public class A2dpSinkStateMachine extends StateMachine {
-    private static final boolean DBG = false;
+    static final String TAG = "A2DPSinkStateMachine";
+    static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
-    static final int CONNECT = 1;
-    static final int DISCONNECT = 2;
-    private 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;
+    //0->99 Events from Outside
+    public static final int CONNECT = 1;
+    public static final int DISCONNECT = 2;
 
-    private static final int IS_INVALID_DEVICE = 0;
-    private static final int IS_VALID_DEVICE = 1;
-    public static final int AVRC_ID_PLAY = 0x44;
-    public static final int AVRC_ID_PAUSE = 0x46;
-    public static final int KEY_STATE_PRESSED = 0;
-    public static final int KEY_STATE_RELEASED = 1;
+    //100->199 Internal Events
+    protected static final int CLEANUP = 100;
+    private static final int CONNECT_TIMEOUT = 101;
 
-    // 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 Connected mConnected;
+    //200->299 Events from Native
+    static final int STACK_EVENT = 200;
 
-    private A2dpSinkService mService;
-    private Context mContext;
-    private BluetoothAdapter mAdapter;
-    private IntentBroadcastHandler mIntentBroadcastHandler;
+    static final int CONNECT_TIMEOUT_MS = 5000;
 
-    private static final int MSG_CONNECTION_STATE_CHANGED = 0;
+    protected final BluetoothDevice mDevice;
+    protected final byte[] mDeviceAddress;
+    protected final A2dpSinkService mService;
+    protected final Disconnected mDisconnected;
+    protected final Connecting mConnecting;
+    protected final Connected mConnected;
+    protected final Disconnecting mDisconnecting;
 
-    private final Object mLockForPatch = new Object();
+    protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED;
+    protected BluetoothAudioConfig mAudioConfig = null;
 
-    // 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 HashMap<BluetoothDevice, BluetoothAudioConfig> mAudioConfigs =
-            new HashMap<BluetoothDevice, BluetoothAudioConfig>();
-
-    static {
-        classInitNative();
-    }
-
-    private A2dpSinkStateMachine(A2dpSinkService svc, Context context) {
-        super("A2dpSinkStateMachine");
-        mService = svc;
-        mContext = context;
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-
-        initNative();
+    A2dpSinkStateMachine(BluetoothDevice device, A2dpSinkService service) {
+        super(TAG);
+        mDevice = device;
+        mDeviceAddress = Utils.getByteAddress(mDevice);
+        mService = service;
+        if (DBG) Log.d(TAG, device.toString());
 
         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);
 
         setInitialState(mDisconnected);
-
-        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-
-        mIntentBroadcastHandler = new IntentBroadcastHandler();
     }
 
-    static A2dpSinkStateMachine make(A2dpSinkService svc, Context context) {
-        Log.d("A2dpSinkStateMachine", "make");
-        A2dpSinkStateMachine a2dpSm = new A2dpSinkStateMachine(svc, context);
-        a2dpSm.start();
-        return a2dpSm;
+    protected String getConnectionStateChangedIntent() {
+        return BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED;
     }
 
-    public void doQuit() {
-        if (DBG) {
-            Log.d("A2dpSinkStateMachine", "Quit");
-        }
-        synchronized (A2dpSinkStateMachine.this) {
-            mStreaming = null;
-        }
-        quitNow();
+    /**
+     * Get the current connection state
+     *
+     * @return current State
+     */
+    public int getState() {
+        return mMostRecentState;
     }
 
-    public void cleanup() {
-        cleanupNative();
-        mAudioConfigs.clear();
+    /**
+     * get current audio config
+     */
+    BluetoothAudioConfig getAudioConfig() {
+        return mAudioConfig;
     }
 
+    /**
+     * Get the underlying device tracked by this state machine
+     *
+     * @return device in focus
+     */
+    public synchronized BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * send the Connect command asynchronously
+     */
+    public final void connect() {
+        sendMessage(CONNECT);
+    }
+
+    /**
+     * send the Disconnect command asynchronously
+     */
+    public final void disconnect() {
+        sendMessage(DISCONNECT);
+    }
+
+    /**
+     * Dump the current State Machine to the string builder.
+     * @param sb output string
+     */
     public void dump(StringBuilder sb) {
-        ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
-        ProfileService.println(sb, "mTargetDevice: " + mTargetDevice);
-        ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice);
-        ProfileService.println(sb, "StateMachine: " + this.toString());
+        ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "("
+                + mDevice.getName() + ") " + this.toString());
     }
 
-    private class Disconnected extends State {
+    @Override
+    protected void unhandledMessage(Message msg) {
+        Log.w(TAG, "unhandledMessage in state " + getCurrentState() + "msg.what=" + msg.what);
+    }
+
+    class Disconnected extends State {
         @Override
         public void enter() {
-            log("Enter Disconnected: " + getCurrentMessage().what);
+            if (DBG) Log.d(TAG, "Enter Disconnected");
+            if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) {
+                sendMessage(CLEANUP);
+            }
+            onConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED);
         }
 
         @Override
         public boolean processMessage(Message message) {
-            log("Disconnected process message: " + message.what);
-            if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) {
-                loge("ERROR: current, target, or mIncomingDevice not null in Disconnected");
-                return NOT_HANDLED;
-            }
-
-            boolean retValue = HANDLED;
             switch (message.what) {
-                case CONNECT:
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                            BluetoothProfile.STATE_DISCONNECTED);
-
-                    if (!connectA2dpNative(getByteAddress(device))) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTING);
-                        break;
-                    }
-
-                    synchronized (A2dpSinkStateMachine.this) {
-                        mTargetDevice = device;
-                        transitionTo(mPending);
-                    }
-                    // TODO(BT) remove CONNECT_TIMEOUT when the stack
-                    //          sends back events consistently
-                    sendMessageDelayed(CONNECT_TIMEOUT, 30000);
-                    break;
-                case DISCONNECT:
-                    // ignore
-                    break;
                 case STACK_EVENT:
-                    StackEvent event = (StackEvent) message.obj;
-                    switch (event.type) {
-                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            processConnectionEvent(event.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;
-        }
-
-        @Override
-        public void exit() {
-            log("Exit Disconnected: " + getCurrentMessage().what);
-        }
-
-        // in Disconnected state
-        private void processConnectionEvent(BluetoothDevice device, int state) {
-            switch (state) {
-                case CONNECTION_STATE_DISCONNECTED:
-                    logw("Ignore A2DP DISCONNECTED event, device: " + device);
-                    break;
-                case CONNECTION_STATE_CONNECTING:
-                    if (okToConnect(device)) {
-                        logi("Incoming A2DP accepted");
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                                BluetoothProfile.STATE_DISCONNECTED);
-                        synchronized (A2dpSinkStateMachine.this) {
-                            mIncomingDevice = device;
-                            transitionTo(mPending);
-                        }
-                    } else {
-                        //reject the connection and stay in Disconnected state itself
-                        logi("Incoming A2DP rejected");
-                        disconnectA2dpNative(getByteAddress(device));
-                    }
-                    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));
-                    }
-                    break;
-                case CONNECTION_STATE_DISCONNECTING:
-                    logw("Ignore HF DISCONNECTING event, device: " + device);
-                    break;
-                default:
-                    loge("Incorrect state: " + state);
-                    break;
-            }
-        }
-    }
-
-    private class Pending extends State {
-        @Override
-        public void enter() {
-            log("Enter Pending: " + getCurrentMessage().what);
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            log("Pending process message: " + message.what);
-
-            boolean retValue = HANDLED;
-            switch (message.what) {
+                    processStackEvent((StackEvent) message.obj);
+                    return true;
                 case CONNECT:
-                    logd("Disconnect before connecting to another target");
-                    break;
-                case CONNECT_TIMEOUT:
-                    onConnectionStateChanged(getByteAddress(mTargetDevice),
-                                             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;
-                        }
-                    }
-                    break;
-                case STACK_EVENT:
-                    StackEvent event = (StackEvent) message.obj;
-                    log("STACK_EVENT " + event.type);
-                    switch (event.type) {
-                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            removeMessages(CONNECT_TIMEOUT);
-                            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;
+                    if (DBG) Log.d(TAG, "Connect");
+                    transitionTo(mConnecting);
+                    return true;
+                case CLEANUP:
+                    mService.removeStateMachine(A2dpSinkStateMachine.this);
+                    return true;
             }
-            return retValue;
-        }
-
-        // 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:
-                    mAudioConfigs.remove(device);
-                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
-                        broadcastConnectionState(mCurrentDevice,
-                                BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_DISCONNECTING);
-                        synchronized (A2dpSinkStateMachine.this) {
-                            mCurrentDevice = null;
-                        }
-
-                        if (mTargetDevice != null) {
-                            if (!connectA2dpNative(getByteAddress(mTargetDevice))) {
-                                broadcastConnectionState(mTargetDevice,
-                                        BluetoothProfile.STATE_DISCONNECTED,
-                                        BluetoothProfile.STATE_CONNECTING);
-                                synchronized (A2dpSinkStateMachine.this) {
-                                    mTargetDevice = null;
-                                    transitionTo(mDisconnected);
-                                }
-                            }
-                        } else {
-                            synchronized (A2dpSinkStateMachine.this) {
-                                mIncomingDevice = null;
-                                transitionTo(mDisconnected);
-                            }
-                        }
-                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
-                        // outgoing connection failed
-                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTING);
-                        synchronized (A2dpSinkStateMachine.this) {
-                            mTargetDevice = null;
-                            transitionTo(mDisconnected);
-                        }
-                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
-                        broadcastConnectionState(mIncomingDevice,
-                                BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTING);
-                        synchronized (A2dpSinkStateMachine.this) {
-                            mIncomingDevice = null;
-                            transitionTo(mDisconnected);
-                        }
-                    } else {
-                        loge("Unknown device Disconnected: " + device);
-                    }
-                    break;
-                case CONNECTION_STATE_CONNECTED:
-                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
-                        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);
-                        }
-                    }
-                    break;
-                case CONNECTION_STATE_CONNECTING:
-                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
-                        log("current device tries to connect back");
-                        // TODO(BT) ignore or reject
-                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
-                        // The stack is connecting to target device or
-                        // there is an incoming connection from the target device at the same time
-                        // we already broadcasted the intent, doing nothing here
-                        log("Stack and target device are connecting");
-                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
-                        loge("Another connecting event on the incoming device");
-                    } else {
-                        // We get an incoming connecting request while Pending
-                        // TODO(BT) is stack handing this case? let's ignore it for now
-                        log("Incoming connection while pending, ignore");
-                    }
-                    break;
-                case CONNECTION_STATE_DISCONNECTING:
-                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
-                        // we already broadcasted the intent, doing nothing here
-                        if (DBG) {
-                            log("stack is disconnecting mCurrentDevice");
-                        }
-                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
-                        loge("TargetDevice is getting disconnected");
-                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
-                        loge("IncomingDevice is getting disconnected");
-                    } else {
-                        loge("Disconnecting unknown device: " + device);
-                    }
-                    break;
-                default:
-                    loge("Incorrect state: " + state);
-                    break;
-            }
-        }
-
-    }
-
-    private class Connected extends State {
-        @Override
-        public void enter() {
-            log("Enter Connected: " + getCurrentMessage().what);
-            // Upon connected, the audio starts out as stopped
-            broadcastAudioState(mCurrentDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
-                    BluetoothA2dpSink.STATE_PLAYING);
-            synchronized (A2dpSinkStateMachine.this) {
-                if (mStreaming == null) {
-                    if (DBG) {
-                        log("Creating New A2dpSinkStreamHandler");
-                    }
-                    mStreaming = new A2dpSinkStreamHandler(A2dpSinkStateMachine.this, mContext);
-                }
-            }
-            if (mStreaming.getAudioFocus() == AudioManager.AUDIOFOCUS_NONE) {
-                informAudioFocusStateNative(0);
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            log("Connected process message: " + message.what);
-            if (mCurrentDevice == null) {
-                loge("ERROR: mCurrentDevice is null in Connected");
-                return NOT_HANDLED;
-            }
-
-            switch (message.what) {
-                case CONNECT:
-                    logd("Disconnect before connecting to another target");
-                break;
-
-                case DISCONNECT: {
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    if (!mCurrentDevice.equals(device)) {
-                        break;
-                    }
-                    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
-                            BluetoothProfile.STATE_CONNECTED);
-                    if (!disconnectA2dpNative(getByteAddress(device))) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
-                                BluetoothProfile.STATE_DISCONNECTED);
-                        break;
-                    }
-                    mPlayingDevice = null;
-                    mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT).sendToTarget();
-                    transitionTo(mPending);
-                }
-                break;
-
-                case STACK_EVENT:
-                    StackEvent event = (StackEvent) message.obj;
-                    switch (event.type) {
-                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            processConnectionEvent(event.device, event.valueInt);
-                            break;
-                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
-                            processAudioStateEvent(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;
-
-                case EVENT_AVRCP_CT_PLAY:
-                    mStreaming.obtainMessage(A2dpSinkStreamHandler.SNK_PLAY).sendToTarget();
-                    break;
-
-                case EVENT_AVRCP_TG_PLAY:
-                    mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_PLAY).sendToTarget();
-                    break;
-
-                case EVENT_AVRCP_CT_PAUSE:
-                    mStreaming.obtainMessage(A2dpSinkStreamHandler.SNK_PAUSE).sendToTarget();
-                    break;
-
-                case EVENT_AVRCP_TG_PAUSE:
-                    mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_PAUSE).sendToTarget();
-                    break;
-
-                case EVENT_REQUEST_FOCUS:
-                    mStreaming.obtainMessage(A2dpSinkStreamHandler.REQUEST_FOCUS).sendToTarget();
-                    break;
-
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-
-        // in Connected state
-        private void processConnectionEvent(BluetoothDevice device, int state) {
-            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);
-                        synchronized (A2dpSinkStateMachine.this) {
-                            // Take care of existing audio focus in the streaming state machine.
-                            mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT)
-                                    .sendToTarget();
-                            mCurrentDevice = null;
-                            transitionTo(mDisconnected);
-                        }
-                    } else {
-                        loge("Disconnected from unknown device: " + device);
-                    }
-                    break;
-                default:
-                    loge("Connection State Device: " + device + " bad state: " + state);
-                    break;
-            }
-        }
-
-        private void processAudioStateEvent(BluetoothDevice device, int state) {
-            if (!mCurrentDevice.equals(device)) {
-                loge("Audio State Device:" + device + "is different from ConnectedDevice:"
-                        + mCurrentDevice);
-                return;
-            }
-            log(" processAudioStateEvent in state " + state);
-            switch (state) {
-                case AUDIO_STATE_STARTED:
-                    mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
-                    break;
-                case AUDIO_STATE_REMOTE_SUSPEND:
-                case AUDIO_STATE_STOPPED:
-                    mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
-                    break;
-                default:
-                    loge("Audio State Device: " + device + " bad state: " + state);
-                    break;
-            }
-        }
-    }
-
-    private void processAudioConfigEvent(BluetoothDevice device, BluetoothAudioConfig audioConfig) {
-        log("processAudioConfigEvent: " + device);
-        mAudioConfigs.put(device, audioConfig);
-        broadcastAudioConfig(device, audioConfig);
-    }
-
-    int getConnectionState(BluetoothDevice device) {
-        if (getCurrentState() == mDisconnected) {
-            return BluetoothProfile.STATE_DISCONNECTED;
-        }
-
-        synchronized (this) {
-            IState currentState = getCurrentState();
-            if (currentState == mPending) {
-                if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
-                    return BluetoothProfile.STATE_CONNECTING;
-                }
-                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
-                    return BluetoothProfile.STATE_DISCONNECTING;
-                }
-                if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
-                    return BluetoothProfile.STATE_CONNECTING; // incoming connection
-                }
-                return BluetoothProfile.STATE_DISCONNECTED;
-            }
-
-            if (currentState == mConnected) {
-                if (mCurrentDevice.equals(device)) {
-                    return BluetoothProfile.STATE_CONNECTED;
-                }
-                return BluetoothProfile.STATE_DISCONNECTED;
-            } else {
-                loge("Bad currentState: " + currentState);
-                return BluetoothProfile.STATE_DISCONNECTED;
-            }
-        }
-    }
-
-    BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
-        return mAudioConfigs.get(device);
-    }
-
-    List<BluetoothDevice> getConnectedDevices() {
-        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
-        synchronized (this) {
-            if (getCurrentState() == mConnected) {
-                devices.add(mCurrentDevice);
-            }
-        }
-        return devices;
-    }
-
-    boolean isPlaying(BluetoothDevice device) {
-        synchronized (this) {
-            if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    // Utility Functions
-    boolean okToConnect(BluetoothDevice device) {
-        AdapterService adapterService = AdapterService.getAdapterService();
-        int priority = mService.getPriority(device);
-
-        // check priority and accept or reject the connection. if priority is undefined
-        // it is likely that our SDP has not completed and peer is initiating the
-        // connection. Allow this connection, provided the device is bonded
-        if ((BluetoothProfile.PRIORITY_OFF < priority) || (
-                (BluetoothProfile.PRIORITY_UNDEFINED == priority) && (device.getBondState()
-                        != BluetoothDevice.BOND_NONE))) {
-            return true;
-        }
-        logw("okToConnect not OK to connect " + device);
-        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);
-    }
-
-    private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
-        Intent intent = new Intent(BluetoothA2dpSink.ACTION_PLAYING_STATE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
-        intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
-//FIXME        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-
-        log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
-    }
-
-    private void broadcastAudioConfig(BluetoothDevice device, BluetoothAudioConfig audioConfig) {
-        Intent intent = new Intent(BluetoothA2dpSink.ACTION_AUDIO_CONFIG_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        intent.putExtra(BluetoothA2dpSink.EXTRA_AUDIO_CONFIG, audioConfig);
-//FIXME        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-
-        log("A2DP Audio Config : device: " + device + " config: " + audioConfig);
-    }
-
-    private byte[] getByteAddress(BluetoothDevice device) {
-        return Utils.getBytesFromAddress(device.getAddress());
-    }
-
-    private void onConnectionStateChanged(byte[] address, int state) {
-        StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
-        event.device = getDevice(address);
-        event.valueInt = state;
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onAudioStateChanged(byte[] address, int state) {
-        StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
-        event.device = getDevice(address);
-        event.valueInt = state;
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
-        StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_CONFIG_CHANGED);
-        event.device = getDevice(address);
-        int channelConfig =
-                (channelCount == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO);
-        event.audioConfig =
-                new BluetoothAudioConfig(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private BluetoothDevice getDevice(byte[] address) {
-        return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
-    }
-
-    private class StackEvent {
-        public int type = EVENT_TYPE_NONE;
-        public BluetoothDevice device = null;
-        public int valueInt = 0;
-        public BluetoothAudioConfig audioConfig = null;
-
-        private StackEvent(int type) {
-            this.type = type;
-        }
-    }
-
-    /** Handles A2DP connection state change intent broadcasts. */
-    private class IntentBroadcastHandler extends Handler {
-
-        private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) {
-            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 =
-                AvrcpControllerService.getAvrcpControllerService();
-        if ((avrcpCtrlService != null) && (mDevice != null)
-                && (avrcpCtrlService.getConnectedDevices().contains(mDevice))) {
-            avrcpCtrlService.sendPassThroughCmd(mDevice,
-                    AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
-                    AvrcpControllerService.KEY_STATE_PRESSED);
-            avrcpCtrlService.sendPassThroughCmd(mDevice,
-                    AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
-                    AvrcpControllerService.KEY_STATE_RELEASED);
-            log(" sendPassThruPlay command sent - ");
-            return true;
-        } else {
-            log("passthru command not sent, connection unavailable");
             return false;
         }
+
+        void processStackEvent(StackEvent event) {
+            switch (event.mType) {
+                case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                    switch (event.mState) {
+                        case StackEvent.CONNECTION_STATE_CONNECTED:
+                            transitionTo(mConnected);
+                            break;
+                        case StackEvent.CONNECTION_STATE_DISCONNECTED:
+                            sendMessage(CLEANUP);
+                            break;
+                    }
+            }
+        }
     }
 
-    // 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;
+    class Connecting extends State {
+        boolean mIncommingConnection = false;
 
-    // Do not modify without updating the HAL bt_av.h files.
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, "Enter Connecting");
+            onConnectionStateChanged(BluetoothProfile.STATE_CONNECTING);
+            sendMessageDelayed(CONNECT_TIMEOUT, CONNECT_TIMEOUT_MS);
 
-    // match up with btav_connection_state_t enum of bt_av.h
-    static final int CONNECTION_STATE_DISCONNECTED = 0;
-    static final int CONNECTION_STATE_CONNECTING = 1;
-    static final int CONNECTION_STATE_CONNECTED = 2;
-    static final int CONNECTION_STATE_DISCONNECTING = 3;
+            if (!mIncommingConnection) {
+                mService.connectA2dpNative(mDeviceAddress);
+            }
 
-    // match up with btav_audio_state_t enum of bt_av.h
-    static final int AUDIO_STATE_REMOTE_SUSPEND = 0;
-    static final int AUDIO_STATE_STOPPED = 1;
-    static final int AUDIO_STATE_STARTED = 2;
+            super.enter();
+        }
 
-    private static native void classInitNative();
+        @Override
+        public boolean processMessage(Message message) {
+            switch (message.what) {
+                case STACK_EVENT:
+                    processStackEvent((StackEvent) message.obj);
+                    return true;
+                case CONNECT_TIMEOUT:
+                    transitionTo(mDisconnected);
+                    return true;
+            }
+            return false;
+        }
 
-    private native void initNative();
+        void processStackEvent(StackEvent event) {
+            switch (event.mType) {
+                case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                    switch (event.mState) {
+                        case StackEvent.CONNECTION_STATE_CONNECTED:
+                            transitionTo(mConnected);
+                            break;
+                        case StackEvent.CONNECTION_STATE_DISCONNECTED:
+                            transitionTo(mDisconnected);
+                            break;
+                    }
+            }
+        }
+        @Override
+        public void exit() {
+            removeMessages(CONNECT_TIMEOUT);
+        }
 
-    private native void cleanupNative();
+    }
 
-    private native boolean connectA2dpNative(byte[] address);
+    class Connected extends State {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, "Enter Connected");
+            onConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
+        }
 
-    private native boolean disconnectA2dpNative(byte[] address);
+        @Override
+        public boolean processMessage(Message message) {
+            switch (message.what) {
+                case DISCONNECT:
+                    transitionTo(mDisconnecting);
+                    mService.disconnectA2dpNative(mDeviceAddress);
+                    return true;
+                case STACK_EVENT:
+                    processStackEvent((StackEvent) message.obj);
+                    return true;
+            }
+            return false;
+        }
 
-    public native void informAudioFocusStateNative(int focusGranted);
+        void processStackEvent(StackEvent event) {
+            switch (event.mType) {
+                case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                    switch (event.mState) {
+                        case StackEvent.CONNECTION_STATE_DISCONNECTING:
+                            transitionTo(mDisconnecting);
+                            break;
+                        case StackEvent.CONNECTION_STATE_DISCONNECTED:
+                            transitionTo(mDisconnected);
+                            break;
+                    }
+                    break;
+                case StackEvent.EVENT_TYPE_AUDIO_CONFIG_CHANGED:
+                    mAudioConfig = new BluetoothAudioConfig(event.mSampleRate, event.mChannelCount,
+                            AudioFormat.ENCODING_PCM_16BIT);
+                    break;
+            }
+        }
+    }
 
-    public native void informAudioTrackGainNative(float focusGranted);
+    protected class Disconnecting extends State {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, "Enter Disconnecting");
+            onConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
+            transitionTo(mDisconnected);
+        }
+    }
+
+    protected void onConnectionStateChanged(int currentState) {
+        if (mMostRecentState == currentState) {
+            return;
+        }
+        if (currentState == BluetoothProfile.STATE_CONNECTED) {
+            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP_SINK);
+        }
+        if (DBG) {
+            Log.d(TAG, "Connection state " + mDevice + ": " + mMostRecentState + "->"
+                    + currentState);
+        }
+        Intent intent = new Intent(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, mMostRecentState);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, currentState);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        mMostRecentState = currentState;
+        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+    }
 }
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
index f442c49..c24eca9 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
@@ -23,12 +23,14 @@
 import android.media.AudioFocusRequest;
 import android.media.AudioManager;
 import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.media.MediaPlayer;
 import android.os.Handler;
 import android.os.Message;
 import android.util.Log;
 
 import com.android.bluetooth.R;
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
 
 import java.util.List;
 
@@ -51,12 +53,12 @@
  * restored.
  */
 public class A2dpSinkStreamHandler extends Handler {
-    private static final boolean DBG = false;
     private static final String TAG = "A2dpSinkStreamHandler";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
     // Configuration Variables
     private static final int DEFAULT_DUCK_PERCENT = 25;
-    private static final int SETTLE_TIMEOUT = 1000;
+    private static final int SETTLE_TIMEOUT = 400;
 
     // Incoming events.
     public static final int SRC_STR_START = 0; // Audio stream from remote device started
@@ -68,7 +70,7 @@
     public static final int DISCONNECT = 6; // Remote device was disconnected
     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 DELAYED_PAUSE = 9; // If a call just started allow stack time to settle
 
     // Used to indicate focus lost
     private static final int STATE_FOCUS_LOST = 0;
@@ -76,7 +78,7 @@
     private static final int STATE_FOCUS_GRANTED = 1;
 
     // Private variables.
-    private A2dpSinkStateMachine mA2dpSinkSm;
+    private A2dpSinkService mA2dpSinkService;
     private Context mContext;
     private AudioManager mAudioManager;
     // Keep track if the remote device is providing audio
@@ -85,6 +87,16 @@
     // Keep track of the relevant audio focus (None, Transient, Gain)
     private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
 
+    // In order for Bluetooth to be considered as an audio source capable of receiving media key
+    // events (In the eyes of MediaSessionService), we need an active MediaPlayer in addition to a
+    // MediaSession. Because of this, the media player below plays an incredibly short, silent audio
+    // sample so that MediaSessionService and AudioPlaybackStateMonitor will believe that we're the
+    // current active player and send the Bluetooth process media events. This allows AVRCP
+    // controller to create a MediaSession and handle the events if it would like. The player and
+    // session requirement is a restriction currently imposed by the media framework code and could
+    // be reconsidered in the future.
+    private MediaPlayer mMediaPlayer = null;
+
     // Focus changes when we are currently holding focus.
     private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
         @Override
@@ -97,12 +109,26 @@
         }
     };
 
-    public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context) {
-        mA2dpSinkSm = a2dpSinkSm;
+    public A2dpSinkStreamHandler(A2dpSinkService a2dpSinkService, Context context) {
+        mA2dpSinkService = a2dpSinkService;
         mContext = context;
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
     }
 
+    void requestAudioFocus(boolean request) {
+        obtainMessage(REQUEST_FOCUS, request).sendToTarget();
+    }
+
+    int getFocusState() {
+        return mAudioFocus;
+    }
+
+    boolean isPlaying() {
+        return (mStreamAvailable
+                && (mAudioFocus == AudioManager.AUDIOFOCUS_GAIN
+                || mAudioFocus == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK));
+    }
+
     @Override
     public void handleMessage(Message message) {
         if (DBG) {
@@ -112,71 +138,45 @@
         switch (message.what) {
             case SRC_STR_START:
                 mStreamAvailable = true;
-                // Always request audio focus if on TV.
-                if (isTvDevice()) {
-                    if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
-                        requestAudioFocus();
-                    }
-                }
-                // Audio stream has started, stop it if we don't have focus.
-                if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
-                    sendAvrcpPause();
-                } else {
-                    startAvrcpUpdates();
+                if (isTvDevice() || shouldRequestFocus()) {
+                    requestAudioFocusIfNone();
                 }
                 break;
 
             case SRC_STR_STOP:
                 // Audio stream has stopped, maintain focus but stop avrcp updates.
-                mStreamAvailable = false;
-                stopAvrcpUpdates();
                 break;
 
             case SNK_PLAY:
                 // Local play command, gain focus and start avrcp updates.
-                if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
-                    requestAudioFocus();
-                }
-                startAvrcpUpdates();
+                requestAudioFocusIfNone();
                 break;
 
             case SNK_PAUSE:
+                mStreamAvailable = false;
                 // Local pause command, maintain focus but stop avrcp updates.
-                stopAvrcpUpdates();
                 break;
 
             case SRC_PLAY:
+                mStreamAvailable = true;
                 // Remote play command.
-                // If is an iot device gain focus and start avrcp updates.
-                if (isIotDevice() || isTvDevice()) {
-                    if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
-                        requestAudioFocus();
-                    }
-                    startAvrcpUpdates();
+                if (isIotDevice() || isTvDevice() || shouldRequestFocus()) {
+                    requestAudioFocusIfNone();
                     break;
                 }
-                // Otherwise, pause if we don't have focus
-                if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
-                    sendAvrcpPause();
-                } else {
-                    startAvrcpUpdates();
-                }
                 break;
 
             case SRC_PAUSE:
+                mStreamAvailable = false;
                 // Remote pause command, stop avrcp updates.
-                stopAvrcpUpdates();
                 break;
 
             case REQUEST_FOCUS:
-                if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
-                    requestAudioFocus();
-                }
+                requestAudioFocusIfNone();
                 break;
 
             case DISCONNECT:
                 // Remote device has disconnected, restore everything to default state.
-                stopAvrcpUpdates();
                 mSentPause = false;
                 break;
 
@@ -184,11 +184,12 @@
                 // message.obj is the newly granted audio focus.
                 switch ((int) message.obj) {
                     case AudioManager.AUDIOFOCUS_GAIN:
+                        removeMessages(DELAYED_PAUSE);
                         // Begin playing audio, if we paused the remote, send a play now.
-                        startAvrcpUpdates();
                         startFluorideStreaming();
                         if (mSentPause) {
-                            sendMessageDelayed(obtainMessage(DELAYED_RESUME), SETTLE_TIMEOUT);
+                            sendAvrcpPlay();
+                            mSentPause = false;
                         }
                         break;
 
@@ -210,11 +211,8 @@
                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                         // Temporary loss of focus, if we are actively streaming pause the remote
                         // and make sure we resume playback when we regain focus.
-                        if (mStreamAvailable) {
-                            sendAvrcpPause();
-                            mSentPause = true;
-                        }
-                        stopFluorideStreaming();
+                        sendMessageDelayed(obtainMessage(DELAYED_PAUSE), SETTLE_TIMEOUT);
+                        setFluorideAudioTrackGain(0);
                         break;
 
                     case AudioManager.AUDIOFOCUS_LOSS:
@@ -226,13 +224,14 @@
                 }
                 break;
 
-            case DELAYED_RESUME:
-                // Resume playback after source and sink states settle.
-                sendAvrcpPlay();
-                mSentPause = false;
+            case DELAYED_PAUSE:
+                if (mStreamAvailable && !inCallFromStreamingDevice()) {
+                    sendAvrcpPause();
+                    mSentPause = true;
+                    mStreamAvailable = false;
+                }
                 break;
 
-
             default:
                 Log.w(TAG, "Received unexpected event: " + message.what);
         }
@@ -241,129 +240,128 @@
     /**
      * Utility functions.
      */
+    private void requestAudioFocusIfNone() {
+        if (DBG) Log.d(TAG, "requestAudioFocusIfNone()");
+        if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
+            requestAudioFocus();
+        }
+        // On the off change mMediaPlayer errors out and dies, we want to make sure we retry this.
+        // This function immediately exits if we have a MediaPlayer object.
+        requestMediaKeyFocus();
+    }
+
     private synchronized int requestAudioFocus() {
+        if (DBG) Log.d(TAG, "requestAudioFocus()");
         // Bluetooth A2DP may carry Music, Audio Books, Navigation, or other sounds so mark content
         // type unknown.
         AudioAttributes streamAttributes =
                 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
                         .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
                         .build();
-        // Bluetooth ducking is handled at the native layer so tell the Audio Manger to notify the
-        // focus change listener via .setWillPauseWhenDucked().
+        // Bluetooth ducking is handled at the native layer at the request of AudioManager.
         AudioFocusRequest focusRequest =
                 new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).setAudioAttributes(
                         streamAttributes)
-                        .setWillPauseWhenDucked(true)
                         .setOnAudioFocusChangeListener(mAudioFocusListener, this)
                         .build();
         int focusRequestStatus = mAudioManager.requestAudioFocus(focusRequest);
         // If the request is granted begin streaming immediately and schedule an upgrade.
         if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
-            startAvrcpUpdates();
             startFluorideStreaming();
             mAudioFocus = AudioManager.AUDIOFOCUS_GAIN;
         }
         return focusRequestStatus;
     }
 
+    /**
+     * Creates a MediaPlayer that plays a silent audio sample so that MediaSessionService will be
+     * aware of the fact that Bluetooth is playing audio.
+     *
+     * This allows the MediaSession in AVRCP Controller to be routed media key events, if we've
+     * chosen to use it.
+     */
+    private synchronized void requestMediaKeyFocus() {
+        if (DBG) Log.d(TAG, "requestMediaKeyFocus()");
+
+        if (mMediaPlayer != null) return;
+
+        AudioAttributes attrs = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .build();
+
+        mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs,
+                mAudioManager.generateAudioSessionId());
+        if (mMediaPlayer == null) {
+            Log.e(TAG, "Failed to initialize media player. You may not get media key events");
+            return;
+        }
+
+        mMediaPlayer.setLooping(false);
+        mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+            Log.e(TAG, "Silent media player error: " + what + ", " + extra);
+            releaseMediaKeyFocus();
+            return false;
+        });
+
+        mMediaPlayer.start();
+        BluetoothMediaBrowserService.setActive(true);
+    }
 
     private synchronized void abandonAudioFocus() {
+        if (DBG) Log.d(TAG, "abandonAudioFocus()");
         stopFluorideStreaming();
+        releaseMediaKeyFocus();
         mAudioManager.abandonAudioFocus(mAudioFocusListener);
         mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
     }
 
+    /**
+     * Destroys the silent audio sample MediaPlayer, notifying MediaSessionService of the fact
+     * we're no longer playing audio.
+     */
+    private synchronized void releaseMediaKeyFocus() {
+        if (DBG) Log.d(TAG, "releaseMediaKeyFocus()");
+        if (mMediaPlayer == null) {
+            return;
+        }
+        BluetoothMediaBrowserService.setActive(false);
+        mMediaPlayer.stop();
+        mMediaPlayer.release();
+        mMediaPlayer = null;
+    }
+
     private void startFluorideStreaming() {
-        mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
-        mA2dpSinkSm.informAudioTrackGainNative(1.0f);
+        mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
+        mA2dpSinkService.informAudioTrackGainNative(1.0f);
     }
 
     private void stopFluorideStreaming() {
-        mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_LOST);
+        mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_LOST);
     }
 
     private void setFluorideAudioTrackGain(float gain) {
-        mA2dpSinkSm.informAudioTrackGainNative(gain);
-    }
-
-    private void startAvrcpUpdates() {
-        // Since AVRCP gets started after A2DP we may need to request it later in cycle.
-        AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
-
-        if (DBG) {
-            Log.d(TAG, "startAvrcpUpdates");
-        }
-        if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
-            avrcpService.startAvrcpUpdates();
-        } else {
-            Log.e(TAG, "startAvrcpUpdates failed because of connection.");
-        }
-    }
-
-    private void stopAvrcpUpdates() {
-        // Since AVRCP gets started after A2DP we may need to request it later in cycle.
-        AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
-
-        if (DBG) {
-            Log.d(TAG, "stopAvrcpUpdates");
-        }
-        if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
-            avrcpService.stopAvrcpUpdates();
-        } else {
-            Log.e(TAG, "stopAvrcpUpdates failed because of connection.");
-        }
+        mA2dpSinkService.informAudioTrackGainNative(gain);
     }
 
     private void sendAvrcpPause() {
-        // Since AVRCP gets started after A2DP we may need to request it later in cycle.
-        AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
-
-        if (DBG) {
-            Log.d(TAG, "sendAvrcpPause");
-        }
-        if (avrcpService != null) {
-            List<BluetoothDevice> connectedDevices = avrcpService.getConnectedDevices();
-            if (!connectedDevices.isEmpty()) {
-                BluetoothDevice targetDevice = connectedDevices.get(0);
-                if (DBG) {
-                    Log.d(TAG, "Pausing AVRCP.");
-                }
-                avrcpService.sendPassThroughCmd(targetDevice,
-                        AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
-                        AvrcpControllerService.KEY_STATE_PRESSED);
-                avrcpService.sendPassThroughCmd(targetDevice,
-                        AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
-                        AvrcpControllerService.KEY_STATE_RELEASED);
-            }
-        } else {
-            Log.e(TAG, "Passthrough not sent, connection un-available.");
-        }
+        BluetoothMediaBrowserService.pause();
     }
 
     private void sendAvrcpPlay() {
-        // Since AVRCP gets started after A2DP we may need to request it later in cycle.
-        AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
+        BluetoothMediaBrowserService.play();
+    }
 
-        if (DBG) {
-            Log.d(TAG, "sendAvrcpPlay");
+    private boolean inCallFromStreamingDevice() {
+        BluetoothDevice targetDevice = null;
+        List<BluetoothDevice> connectedDevices = mA2dpSinkService.getConnectedDevices();
+        if (!connectedDevices.isEmpty()) {
+            targetDevice = connectedDevices.get(0);
         }
-        if (avrcpService != null) {
-            List<BluetoothDevice> connectedDevices = avrcpService.getConnectedDevices();
-            if (!connectedDevices.isEmpty()) {
-                BluetoothDevice targetDevice = connectedDevices.get(0);
-                if (DBG) {
-                    Log.d(TAG, "Playing AVRCP.");
-                }
-                avrcpService.sendPassThroughCmd(targetDevice,
-                        AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
-                        AvrcpControllerService.KEY_STATE_PRESSED);
-                avrcpService.sendPassThroughCmd(targetDevice,
-                        AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
-                        AvrcpControllerService.KEY_STATE_RELEASED);
-            }
-        } else {
-            Log.e(TAG, "Passthrough not sent, connection un-available.");
+        HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
+        if (targetDevice != null && headsetClientService != null) {
+            return headsetClientService.getCurrentCalls(targetDevice).size() > 0;
         }
+        return false;
     }
 
     synchronized int getAudioFocus() {
@@ -378,4 +376,9 @@
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
     }
 
+    private boolean shouldRequestFocus() {
+        return mContext.getResources()
+                .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus);
+    }
+
 }
diff --git a/src/com/android/bluetooth/a2dpsink/StackEvent.java b/src/com/android/bluetooth/a2dpsink/StackEvent.java
new file mode 100644
index 0000000..68a6ce9
--- /dev/null
+++ b/src/com/android/bluetooth/a2dpsink/StackEvent.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.a2dpsink;
+
+import android.bluetooth.BluetoothDevice;
+
+final class StackEvent {
+    // Event types for STACK_EVENT message
+    static final int EVENT_TYPE_NONE = 0;
+    static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+    static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
+    static final int EVENT_TYPE_AUDIO_CONFIG_CHANGED = 3;
+
+    // match up with btav_connection_state_t enum of bt_av.h
+    static final int CONNECTION_STATE_DISCONNECTED = 0;
+    static final int CONNECTION_STATE_CONNECTING = 1;
+    static final int CONNECTION_STATE_CONNECTED = 2;
+    static final int CONNECTION_STATE_DISCONNECTING = 3;
+
+    // match up with btav_audio_state_t enum of bt_av.h
+    static final int AUDIO_STATE_REMOTE_SUSPEND = 0;
+    static final int AUDIO_STATE_STOPPED = 1;
+    static final int AUDIO_STATE_STARTED = 2;
+
+    int mType = EVENT_TYPE_NONE;
+    BluetoothDevice mDevice = null;
+    int mState = 0;
+    int mSampleRate = 0;
+    int mChannelCount = 0;
+
+    private StackEvent(int type) {
+        this.mType = type;
+    }
+
+    @Override
+    public String toString() {
+        switch (mType) {
+            case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                return "EVENT_TYPE_CONNECTION_STATE_CHANGED " + mState;
+            case EVENT_TYPE_AUDIO_STATE_CHANGED:
+                return "EVENT_TYPE_AUDIO_STATE_CHANGED " + mState;
+            case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
+                return "EVENT_TYPE_AUDIO_CONFIG_CHANGED " + mSampleRate + ":" + mChannelCount;
+            default:
+                return "Unknown";
+        }
+    }
+
+    static StackEvent connectionStateChanged(BluetoothDevice device, int state) {
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.mDevice = device;
+        event.mState = state;
+        return event;
+    }
+
+    static StackEvent audioStateChanged(BluetoothDevice device, int state) {
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
+        event.mDevice = device;
+        event.mState = state;
+        return event;
+    }
+
+    static StackEvent audioConfigChanged(BluetoothDevice device, int sampleRate,
+            int channelCount) {
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_CONFIG_CHANGED);
+        event.mDevice = device;
+        event.mSampleRate = sampleRate;
+        event.mChannelCount = channelCount;
+        return event;
+    }
+}
diff --git a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
deleted file mode 100644
index 739f17c..0000000
--- a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
+++ /dev/null
@@ -1,561 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.a2dpsink.mbs;
-
-import android.bluetooth.BluetoothAvrcpController;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.MediaMetadata;
-import android.media.browse.MediaBrowser;
-import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Parcelable;
-import android.service.media.MediaBrowserService;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.bluetooth.R;
-import com.android.bluetooth.a2dpsink.A2dpSinkService;
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
-import com.android.bluetooth.avrcpcontroller.BrowseTree;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Implements the MediaBrowserService interface to AVRCP and A2DP
- *
- * This service provides a means for external applications to access A2DP and AVRCP.
- * The applications are expected to use MediaBrowser (see API) and all the music
- * browsing/playback/metadata can be controlled via MediaBrowser and MediaController.
- *
- * The current behavior of MediaSession exposed by this service is as follows:
- * 1. MediaSession is active (i.e. SystemUI and other overview UIs can see updates) when device is
- * connected and first starts playing. Before it starts playing we do not active the session.
- * 1.1 The session is active throughout the duration of connection.
- * 2. The session is de-activated when the device disconnects. It will be connected again when (1)
- * happens.
- */
-public class A2dpMediaBrowserService extends MediaBrowserService {
-    private static final String TAG = "A2dpMediaBrowserService";
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false;
-
-    private static final String UNKNOWN_BT_AUDIO = "__UNKNOWN_BT_AUDIO__";
-    private static final float PLAYBACK_SPEED = 1.0f;
-
-    // Message sent when A2DP device is disconnected.
-    private static final int MSG_DEVICE_DISCONNECT = 0;
-    // Message sent when A2DP device is connected.
-    private static final int MSG_DEVICE_CONNECT = 2;
-    // Message sent when we recieve a TRACK update from AVRCP profile over a connected A2DP device.
-    private static final int MSG_TRACK = 4;
-    // Internal message sent to trigger a AVRCP action.
-    private static final int MSG_AVRCP_PASSTHRU = 5;
-    // Internal message to trigger a getplaystatus command to remote.
-    private static final int MSG_AVRCP_GET_PLAY_STATUS_NATIVE = 6;
-    // Message sent when AVRCP browse is connected.
-    private static final int MSG_DEVICE_BROWSE_CONNECT = 7;
-    // Message sent when AVRCP browse is disconnected.
-    private static final int MSG_DEVICE_BROWSE_DISCONNECT = 8;
-    // Message sent when folder list is fetched.
-    private static final int MSG_FOLDER_LIST = 9;
-
-    // Custom actions for PTS testing.
-    private static final String CUSTOM_ACTION_VOL_UP =
-            "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_VOL_UP";
-    private static final String CUSTOM_ACTION_VOL_DN =
-            "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_VOL_DN";
-    private static final String CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE =
-            "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE";
-
-    private MediaSession mSession;
-    private MediaMetadata mA2dpMetadata;
-
-    private AvrcpControllerService mAvrcpCtrlSrvc;
-    private boolean mBrowseConnected = false;
-    private BluetoothDevice mA2dpDevice = null;
-    private A2dpSinkService mA2dpSinkService = null;
-    private Handler mAvrcpCommandQueue;
-    private final Map<String, Result<List<MediaItem>>> mParentIdToRequestMap = new HashMap<>();
-
-    // Browsing related structures.
-    private List<MediaItem> mNowPlayingList = null;
-
-    private long mTransportControlFlags = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY
-            | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS;
-
-    private static final class AvrcpCommandQueueHandler extends Handler {
-        WeakReference<A2dpMediaBrowserService> mInst;
-
-        AvrcpCommandQueueHandler(Looper looper, A2dpMediaBrowserService sink) {
-            super(looper);
-            mInst = new WeakReference<A2dpMediaBrowserService>(sink);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            A2dpMediaBrowserService inst = mInst.get();
-            if (inst == null) {
-                Log.e(TAG, "Parent class has died; aborting.");
-                return;
-            }
-
-            switch (msg.what) {
-                case MSG_DEVICE_CONNECT:
-                    inst.msgDeviceConnect((BluetoothDevice) msg.obj);
-                    break;
-                case MSG_DEVICE_DISCONNECT:
-                    inst.msgDeviceDisconnect((BluetoothDevice) msg.obj);
-                    break;
-                case MSG_TRACK:
-                    Pair<PlaybackState, MediaMetadata> pair =
-                            (Pair<PlaybackState, MediaMetadata>) (msg.obj);
-                    inst.msgTrack(pair.first, pair.second);
-                    break;
-                case MSG_AVRCP_PASSTHRU:
-                    inst.msgPassThru((int) msg.obj);
-                    break;
-                case MSG_AVRCP_GET_PLAY_STATUS_NATIVE:
-                    inst.msgGetPlayStatusNative();
-                    break;
-                case MSG_DEVICE_BROWSE_CONNECT:
-                    inst.msgDeviceBrowseConnect((BluetoothDevice) msg.obj);
-                    break;
-                case MSG_DEVICE_BROWSE_DISCONNECT:
-                    inst.msgDeviceBrowseDisconnect((BluetoothDevice) msg.obj);
-                    break;
-                case MSG_FOLDER_LIST:
-                    inst.msgFolderList((Intent) msg.obj);
-                    break;
-                default:
-                    Log.e(TAG, "Message not handled " + msg);
-            }
-        }
-    }
-
-    @Override
-    public void onCreate() {
-        if (DBG) Log.d(TAG, "onCreate");
-        super.onCreate();
-
-        mSession = new MediaSession(this, TAG);
-        setSessionToken(mSession.getSessionToken());
-        mSession.setCallback(mSessionCallbacks);
-        mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
-                | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
-        mSession.setActive(true);
-        mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this);
-
-        refreshInitialPlayingState();
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
-        filter.addAction(AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
-        filter.addAction(AvrcpControllerService.ACTION_TRACK_EVENT);
-        filter.addAction(AvrcpControllerService.ACTION_FOLDER_LIST);
-        registerReceiver(mBtReceiver, filter);
-
-        synchronized (this) {
-            mParentIdToRequestMap.clear();
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        if (DBG) Log.d(TAG, "onDestroy");
-        mSession.release();
-        unregisterReceiver(mBtReceiver);
-        super.onDestroy();
-    }
-
-    @Override
-    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-        return new BrowserRoot(BrowseTree.ROOT, null);
-    }
-
-    @Override
-    public synchronized void onLoadChildren(final String parentMediaId,
-            final Result<List<MediaItem>> result) {
-        if (mAvrcpCtrlSrvc == null) {
-            Log.w(TAG, "AVRCP not yet connected.");
-            result.sendResult(Collections.emptyList());
-            return;
-        }
-
-        if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
-        if (!mAvrcpCtrlSrvc.getChildren(mA2dpDevice, parentMediaId, 0, 0xff)) {
-            result.sendResult(Collections.emptyList());
-            return;
-        }
-
-        // Since we are using this thread from a binder thread we should make sure that
-        // we synchronize against other such asynchronous calls.
-        synchronized (this) {
-            mParentIdToRequestMap.put(parentMediaId, result);
-        }
-        result.detach();
-    }
-
-    @Override
-    public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) {
-    }
-
-    // Media Session Stuff.
-    private MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
-        @Override
-        public void onPlay() {
-            if (DBG) Log.d(TAG, "onPlay");
-            mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
-                    AvrcpControllerService.PASS_THRU_CMD_ID_PLAY).sendToTarget();
-            // TRACK_EVENT should be fired eventually and the UI should be hence updated.
-        }
-
-        @Override
-        public void onPause() {
-            if (DBG) Log.d(TAG, "onPause");
-            mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
-                    AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE).sendToTarget();
-            // TRACK_EVENT should be fired eventually and the UI should be hence updated.
-        }
-
-        @Override
-        public void onSkipToNext() {
-            if (DBG) Log.d(TAG, "onSkipToNext");
-            mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
-                    AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD).sendToTarget();
-            // TRACK_EVENT should be fired eventually and the UI should be hence updated.
-        }
-
-        @Override
-        public void onSkipToPrevious() {
-            if (DBG) Log.d(TAG, "onSkipToPrevious");
-            mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
-                    AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD).sendToTarget();
-            // TRACK_EVENT should be fired eventually and the UI should be hence updated.
-        }
-
-        @Override
-        public void onStop() {
-            if (DBG) Log.d(TAG, "onStop");
-            mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
-                    AvrcpControllerService.PASS_THRU_CMD_ID_STOP).sendToTarget();
-        }
-
-        @Override
-        public void onPrepare() {
-            if (DBG) Log.d(TAG, "onPrepare");
-            if (mA2dpSinkService != null) {
-                mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
-            }
-        }
-
-        @Override
-        public void onRewind() {
-            if (DBG) Log.d(TAG, "onRewind");
-            mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
-                    AvrcpControllerService.PASS_THRU_CMD_ID_REWIND).sendToTarget();
-            // TRACK_EVENT should be fired eventually and the UI should be hence updated.
-        }
-
-        @Override
-        public void onFastForward() {
-            if (DBG) Log.d(TAG, "onFastForward");
-            mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
-                    AvrcpControllerService.PASS_THRU_CMD_ID_FF).sendToTarget();
-            // TRACK_EVENT should be fired eventually and the UI should be hence updated.
-        }
-
-        @Override
-        public void onPlayFromMediaId(String mediaId, Bundle extras) {
-            synchronized (A2dpMediaBrowserService.this) {
-                // Play the item if possible.
-                mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
-
-                // Since we request explicit playback here we should start the updates to UI.
-                mAvrcpCtrlSrvc.startAvrcpUpdates();
-            }
-
-            // TRACK_EVENT should be fired eventually and the UI should be hence updated.
-        }
-
-        // Support VOL UP and VOL DOWN events for PTS testing.
-        @Override
-        public void onCustomAction(String action, Bundle extras) {
-            if (DBG) Log.d(TAG, "onCustomAction " + action);
-            if (CUSTOM_ACTION_VOL_UP.equals(action)) {
-                mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
-                        AvrcpControllerService.PASS_THRU_CMD_ID_VOL_UP).sendToTarget();
-            } else if (CUSTOM_ACTION_VOL_DN.equals(action)) {
-                mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
-                        AvrcpControllerService.PASS_THRU_CMD_ID_VOL_DOWN).sendToTarget();
-            } else if (CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE.equals(action)) {
-                mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_GET_PLAY_STATUS_NATIVE).sendToTarget();
-            } else {
-                Log.w(TAG, "Custom action " + action + " not supported.");
-            }
-        }
-    };
-
-    private BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DBG) Log.d(TAG, "onReceive intent=" + intent);
-            String action = intent.getAction();
-            BluetoothDevice btDev =
-                    (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
-
-            if (BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
-                if (DBG) {
-                    Log.d(TAG, "handleConnectionStateChange: newState="
-                            + state + " btDev=" + btDev);
-                }
-
-                // Connected state will be handled when AVRCP BluetoothProfile gets connected.
-                if (state == BluetoothProfile.STATE_CONNECTED) {
-                    mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_CONNECT, btDev).sendToTarget();
-                } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
-                    // Set the playback state to unconnected.
-                    mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_DISCONNECT, btDev).sendToTarget();
-                    // If we have been pushing updates via the session then stop sending them since
-                    // we are not connected anymore.
-                    if (mSession.isActive()) {
-                        mSession.setActive(false);
-                    }
-                }
-            } else if (AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED.equals(
-                    action)) {
-                if (state == BluetoothProfile.STATE_CONNECTED) {
-                    mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_BROWSE_CONNECT, btDev)
-                            .sendToTarget();
-                } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
-                    mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_BROWSE_DISCONNECT, btDev)
-                            .sendToTarget();
-                }
-            } else if (AvrcpControllerService.ACTION_TRACK_EVENT.equals(action)) {
-                PlaybackState pbb =
-                        intent.getParcelableExtra(AvrcpControllerService.EXTRA_PLAYBACK);
-                MediaMetadata mmd =
-                        intent.getParcelableExtra(AvrcpControllerService.EXTRA_METADATA);
-                mAvrcpCommandQueue.obtainMessage(MSG_TRACK,
-                        new Pair<PlaybackState, MediaMetadata>(pbb, mmd)).sendToTarget();
-            } else if (AvrcpControllerService.ACTION_FOLDER_LIST.equals(action)) {
-                mAvrcpCommandQueue.obtainMessage(MSG_FOLDER_LIST, intent).sendToTarget();
-            }
-        }
-    };
-
-    private synchronized void msgDeviceConnect(BluetoothDevice device) {
-        if (DBG) Log.d(TAG, "msgDeviceConnect");
-        // 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;
-        }
-        refreshInitialPlayingState();
-    }
-
-
-    // Refresh the UI if we have a connected device and AVRCP is initialized.
-    private synchronized void refreshInitialPlayingState() {
-        if (mA2dpDevice == null) {
-            if (DBG) Log.d(TAG, "device " + mA2dpDevice);
-            return;
-        }
-
-        List<BluetoothDevice> devices = mAvrcpCtrlSrvc.getConnectedDevices();
-        if (devices.size() == 0) {
-            Log.w(TAG, "No devices connected yet");
-            return;
-        }
-
-        if (mA2dpDevice != null && !mA2dpDevice.equals(devices.get(0))) {
-            Log.w(TAG, "A2dp device : " + mA2dpDevice + " avrcp device " + devices.get(0));
-            return;
-        }
-        mA2dpDevice = devices.get(0);
-        mA2dpSinkService = A2dpSinkService.getA2dpSinkService();
-
-        PlaybackState playbackState = mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice);
-        // Add actions required for playback and rebuild the object.
-        PlaybackState.Builder pbb = new PlaybackState.Builder(playbackState);
-        playbackState = pbb.setActions(mTransportControlFlags).build();
-
-        MediaMetadata mediaMetadata = mAvrcpCtrlSrvc.getMetaData(mA2dpDevice);
-        if (VDBG) {
-            Log.d(TAG, "Media metadata " + mediaMetadata + " playback state " + playbackState);
-        }
-        mSession.setMetadata(mAvrcpCtrlSrvc.getMetaData(mA2dpDevice));
-        mSession.setPlaybackState(playbackState);
-    }
-
-    private void msgDeviceDisconnect(BluetoothDevice device) {
-        if (DBG) Log.d(TAG, "msgDeviceDisconnect");
-        if (mA2dpDevice == null) {
-            Log.w(TAG, "Already disconnected - nothing to do here.");
-            return;
-        } else if (!mA2dpDevice.equals(device)) {
-            Log.e(TAG,
-                    "Not the right device to disconnect current " + mA2dpDevice + " dc " + device);
-            return;
-        }
-
-        // Unset the session.
-        PlaybackState.Builder pbb = new PlaybackState.Builder();
-        pbb = pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
-                PLAYBACK_SPEED)
-                .setActions(mTransportControlFlags)
-                .setErrorMessage(getString(R.string.bluetooth_disconnected));
-        mSession.setPlaybackState(pbb.build());
-
-        // Set device to null.
-        mA2dpDevice = null;
-        mBrowseConnected = false;
-        // update playerList.
-        notifyChildrenChanged("__ROOT__");
-    }
-
-    private void msgTrack(PlaybackState pb, MediaMetadata mmd) {
-        if (VDBG) Log.d(TAG, "msgTrack: playback: " + pb + " mmd: " + mmd);
-        // Log the current track position/content.
-        MediaController controller = mSession.getController();
-        PlaybackState prevPS = controller.getPlaybackState();
-        MediaMetadata prevMM = controller.getMetadata();
-
-        if (prevPS != null) {
-            Log.d(TAG, "prevPS " + prevPS);
-        }
-
-        if (prevMM != null) {
-            String title = prevMM.getString(MediaMetadata.METADATA_KEY_TITLE);
-            long trackLen = prevMM.getLong(MediaMetadata.METADATA_KEY_DURATION);
-            if (VDBG) Log.d(TAG, "prev MM title " + title + " track len " + trackLen);
-        }
-
-        if (mmd != null) {
-            if (VDBG) Log.d(TAG, "msgTrack() mmd " + mmd.getDescription());
-            mSession.setMetadata(mmd);
-        }
-
-        if (pb != null) {
-            if (DBG) Log.d(TAG, "msgTrack() playbackstate " + pb);
-            PlaybackState.Builder pbb = new PlaybackState.Builder(pb);
-            pb = pbb.setActions(mTransportControlFlags).build();
-            mSession.setPlaybackState(pb);
-
-            // If we are now playing then we should start pushing updates via MediaSession so that
-            // external UI (such as SystemUI) can show the currently playing music.
-            if (pb.getState() == PlaybackState.STATE_PLAYING && !mSession.isActive()) {
-                mSession.setActive(true);
-            }
-        }
-    }
-
-    private synchronized void msgPassThru(int cmd) {
-        if (DBG) Log.d(TAG, "msgPassThru " + cmd);
-        if (mA2dpDevice == null) {
-            // We should have already disconnected - ignore this message.
-            Log.w(TAG, "Already disconnected ignoring.");
-            return;
-        }
-
-        // Send the pass through.
-        mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
-                AvrcpControllerService.KEY_STATE_PRESSED);
-        mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
-                AvrcpControllerService.KEY_STATE_RELEASED);
-    }
-
-    private synchronized void msgGetPlayStatusNative() {
-        if (DBG) Log.d(TAG, "msgGetPlayStatusNative");
-        if (mA2dpDevice == null) {
-            // We should have already disconnected - ignore this message.
-            Log.w(TAG, "Already disconnected ignoring.");
-            return;
-        }
-
-        // Ask for a non cached version.
-        mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice, false);
-    }
-
-    private void msgDeviceBrowseConnect(BluetoothDevice device) {
-        if (DBG) Log.d(TAG, "msgDeviceBrowseConnect device " + device);
-        // We should already be connected to this device over A2DP.
-        if (!device.equals(mA2dpDevice)) {
-            Log.e(TAG, "Browse connected over different device a2dp " + mA2dpDevice + " browse "
-                    + device);
-            return;
-        }
-        mBrowseConnected = true;
-        // update playerList
-        notifyChildrenChanged("__ROOT__");
-    }
-
-    private void msgFolderList(Intent intent) {
-        // Parse the folder list for children list and id.
-        List<Parcelable> extraParcelableList =
-                (ArrayList<Parcelable>) intent.getParcelableArrayListExtra(
-                        AvrcpControllerService.EXTRA_FOLDER_LIST);
-        List<MediaItem> folderList = new ArrayList<MediaItem>();
-        for (Parcelable p : extraParcelableList) {
-            folderList.add((MediaItem) p);
-        }
-
-        String id = intent.getStringExtra(AvrcpControllerService.EXTRA_FOLDER_ID);
-        if (VDBG) Log.d(TAG, "Parent: " + id + " Folder list: " + folderList);
-        synchronized (this) {
-            // If we have a result object then we should send the result back
-            // to client since it is blocking otherwise we may have gotten more items
-            // from remote device, hence let client know to fetch again.
-            Result<List<MediaItem>> results = mParentIdToRequestMap.remove(id);
-            if (results == null) {
-                Log.w(TAG, "Request no longer exists, notifying that children changed.");
-                notifyChildrenChanged(id);
-            } else {
-                results.sendResult(folderList);
-            }
-        }
-    }
-
-    private void msgDeviceBrowseDisconnect(BluetoothDevice device) {
-        if (DBG) Log.d(TAG, "msgDeviceBrowseDisconnect device " + device);
-        // Disconnect only if mA2dpDevice is non null
-        if (!device.equals(mA2dpDevice)) {
-            Log.w(TAG, "Browse disconnecting from different device a2dp " + mA2dpDevice + " browse "
-                    + device);
-            return;
-        }
-        mBrowseConnected = false;
-    }
-}
diff --git a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
deleted file mode 100644
index 1d4810a..0000000
--- a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
+++ /dev/null
@@ -1,601 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.avrcp;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.media.session.MediaSession;
-import android.media.session.MediaSession.QueueItem;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.android.bluetooth.Utils;
-import com.android.bluetooth.btservice.ProfileService;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/*************************************************************************************************
- * Provides functionality required for Addressed Media Player, like Now Playing List related
- * browsing commands, control commands to the current addressed player(playItem, play, pause, etc)
- * Acts as an Interface to communicate with media controller APIs for NowPlayingItems.
- ************************************************************************************************/
-
-public class AddressedMediaPlayer {
-    private static final String TAG = "AddressedMediaPlayer";
-    private static final Boolean DEBUG = false;
-
-    private static final long SINGLE_QID = 1;
-    private static final String UNKNOWN_TITLE = "(unknown)";
-
-    static private final String GPM_BUNDLE_METADATA_KEY =
-            "com.google.android.music.mediasession.music_metadata";
-
-    private AvrcpMediaRspInterface mMediaInterface;
-    @NonNull private List<MediaSession.QueueItem> mNowPlayingList;
-
-    private final List<MediaSession.QueueItem> mEmptyNowPlayingList;
-
-    private long mLastTrackIdSent;
-
-    public AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface) {
-        mEmptyNowPlayingList = new ArrayList<MediaSession.QueueItem>();
-        mNowPlayingList = mEmptyNowPlayingList;
-        mMediaInterface = mediaInterface;
-        mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
-    }
-
-    void cleanup() {
-        if (DEBUG) {
-            Log.v(TAG, "cleanup");
-        }
-        mNowPlayingList = mEmptyNowPlayingList;
-        mMediaInterface = null;
-        mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
-    }
-
-    /* get now playing list from addressed player */
-    void getFolderItemsNowPlaying(byte[] bdaddr, AvrcpCmd.FolderItemsCmd reqObj,
-            @Nullable MediaController mediaController) {
-        if (mediaController == null) {
-            // No players (if a player exists, we would have selected it)
-            Log.e(TAG, "mediaController = null, sending no available players response");
-            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_AVBL_PLAY, null);
-            return;
-        }
-        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
-        getFolderItemsFilterAttr(bdaddr, reqObj, items, AvrcpConstants.BTRC_SCOPE_NOW_PLAYING,
-                reqObj.mStartItem, reqObj.mEndItem, mediaController);
-    }
-
-    /* get item attributes for item in now playing list */
-    void getItemAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd itemAttr,
-            @Nullable MediaController mediaController) {
-        int status = AvrcpConstants.RSP_NO_ERROR;
-        long mediaId = ByteBuffer.wrap(itemAttr.mUid).getLong();
-        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
-
-        // NOTE: this is out-of-spec (AVRCP 1.6.1 sec 6.10.4.3, p90) but we answer it anyway
-        // because some CTs ask for it.
-        if (Arrays.equals(itemAttr.mUid, AvrcpConstants.TRACK_IS_SELECTED)) {
-            mediaId = getActiveQueueItemId(mediaController);
-            if (DEBUG) {
-                Log.d(TAG, "getItemAttr: Remote requests for now playing contents, sending UID: "
-                        + mediaId);
-            }
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "getItemAttr-UID: 0x" + Utils.byteArrayToString(itemAttr.mUid));
-        }
-        for (MediaSession.QueueItem item : items) {
-            if (item.getQueueId() == mediaId) {
-                getItemAttrFilterAttr(bdaddr, itemAttr, item, mediaController);
-                return;
-            }
-        }
-
-        // Couldn't find it, so the id is invalid
-        mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_INV_ITEM, null);
-    }
-
-    /* Refresh and get the queue of now playing.
-     */
-    @NonNull
-    List<MediaSession.QueueItem> updateNowPlayingList(@Nullable MediaController mediaController) {
-        if (mediaController == null) {
-            return mEmptyNowPlayingList;
-        }
-        List<MediaSession.QueueItem> items = mediaController.getQueue();
-        if (items == null) {
-            Log.i(TAG, "null queue from " + mediaController.getPackageName()
-                    + ", constructing single-item list");
-
-            // Because we are database-unaware, we can just number the item here whatever we want
-            // because they have to re-poll it every time.
-            MediaMetadata metadata = mediaController.getMetadata();
-            if (metadata == null) {
-                Log.w(TAG, "Controller has no metadata!? Making an empty one");
-                metadata = (new MediaMetadata.Builder()).build();
-            }
-
-            MediaDescription.Builder bob = new MediaDescription.Builder();
-            MediaDescription desc = metadata.getDescription();
-
-            // set the simple ones that MediaMetadata builds for us
-            bob.setMediaId(desc.getMediaId());
-            bob.setTitle(desc.getTitle());
-            bob.setSubtitle(desc.getSubtitle());
-            bob.setDescription(desc.getDescription());
-            // fill the ones that we use later
-            bob.setExtras(fillBundle(metadata, desc.getExtras()));
-
-            // build queue item with the new metadata
-            MediaSession.QueueItem current = new QueueItem(bob.build(), SINGLE_QID);
-
-            items = new ArrayList<MediaSession.QueueItem>();
-            items.add(current);
-        }
-
-        if (!items.equals(mNowPlayingList)) {
-            sendNowPlayingListChanged();
-        }
-        mNowPlayingList = items;
-
-        return mNowPlayingList;
-    }
-
-    private void sendNowPlayingListChanged() {
-        if (mMediaInterface == null) {
-            return;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "sendNowPlayingListChanged()");
-        }
-        mMediaInterface.nowPlayingChangedRsp(AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
-    }
-
-    private Bundle fillBundle(MediaMetadata metadata, Bundle currentExtras) {
-        if (metadata == null) {
-            Log.i(TAG, "fillBundle: metadata is null");
-            return currentExtras;
-        }
-
-        Bundle bundle = currentExtras;
-        if (bundle == null) {
-            bundle = new Bundle();
-        }
-
-        String[] stringKeys = {
-                MediaMetadata.METADATA_KEY_TITLE,
-                MediaMetadata.METADATA_KEY_ARTIST,
-                MediaMetadata.METADATA_KEY_ALBUM,
-                MediaMetadata.METADATA_KEY_GENRE
-        };
-        for (String key : stringKeys) {
-            String current = bundle.getString(key);
-            if (current == null) {
-                bundle.putString(key, metadata.getString(key));
-            }
-        }
-
-        String[] longKeys = {
-                MediaMetadata.METADATA_KEY_TRACK_NUMBER,
-                MediaMetadata.METADATA_KEY_NUM_TRACKS,
-                MediaMetadata.METADATA_KEY_DURATION
-        };
-        for (String key : longKeys) {
-            if (!bundle.containsKey(key)) {
-                bundle.putLong(key, metadata.getLong(key));
-            }
-        }
-        return bundle;
-    }
-
-    /* Instructs media player to play particular media item */
-    void playItem(byte[] bdaddr, byte[] uid, @Nullable MediaController mediaController) {
-        long qid = ByteBuffer.wrap(uid).getLong();
-        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
-
-        if (mediaController == null) {
-            Log.e(TAG, "No mediaController when PlayItem " + qid + " requested");
-            mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
-            return;
-        }
-
-        MediaController.TransportControls mediaControllerCntrl =
-                mediaController.getTransportControls();
-
-        if (items == null) {
-            Log.w(TAG, "nowPlayingItems is null");
-            mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
-            return;
-        }
-
-        for (MediaSession.QueueItem item : items) {
-            if (qid == item.getQueueId()) {
-                if (DEBUG) {
-                    Log.d(TAG, "Skipping to ID " + qid);
-                }
-                mediaControllerCntrl.skipToQueueItem(qid);
-                mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR);
-                return;
-            }
-        }
-
-        Log.w(TAG, "Invalid now playing Queue ID " + qid);
-        mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INV_ITEM);
-    }
-
-    void getTotalNumOfItems(byte[] bdaddr, @Nullable MediaController mediaController) {
-        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
-        if (DEBUG) {
-            Log.d(TAG, "getTotalNumOfItems: " + items.size() + " items.");
-        }
-        mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, items.size());
-    }
-
-    void sendTrackChangeWithId(int type, @Nullable MediaController mediaController) {
-        Log.d(TAG, "sendTrackChangeWithId (" + type + "): controller " + mediaController);
-        long qid = getActiveQueueItemId(mediaController);
-        byte[] track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
-        // The nowPlayingList changed: the new list has the full data for the current item
-        mMediaInterface.trackChangedRsp(type, track);
-        mLastTrackIdSent = qid;
-    }
-
-    /*
-     * helper method to check if startItem and endItem index is with range of
-     * MediaItem list. (Resultset containing all items in current path)
-     */
-    @Nullable
-    private List<MediaSession.QueueItem> getQueueSubset(@NonNull List<MediaSession.QueueItem> items,
-            long startItem, long endItem) {
-        if (endItem > items.size()) {
-            endItem = items.size() - 1;
-        }
-        if (startItem > Integer.MAX_VALUE) {
-            startItem = Integer.MAX_VALUE;
-        }
-        try {
-            List<MediaSession.QueueItem> selected =
-                    items.subList((int) startItem, (int) Math.min(items.size(), endItem + 1));
-            if (selected.isEmpty()) {
-                Log.i(TAG, "itemsSubList is empty.");
-                return null;
-            }
-            return selected;
-        } catch (IndexOutOfBoundsException ex) {
-            Log.i(TAG, "Range (" + startItem + ", " + endItem + ") invalid");
-        } catch (IllegalArgumentException ex) {
-            Log.i(TAG, "Range start " + startItem + " > size (" + items.size() + ")");
-        }
-        return null;
-    }
-
-    /*
-     * helper method to filter required attibutes before sending GetFolderItems
-     * response
-     */
-    private void getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd folderItemsReqObj,
-            @NonNull List<MediaSession.QueueItem> items, byte scope, long startItem, long endItem,
-            @NonNull MediaController mediaController) {
-        if (DEBUG) {
-            Log.d(TAG,
-                    "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = " + endItem);
-        }
-
-        List<MediaSession.QueueItem> resultItems = getQueueSubset(items, startItem, endItem);
-        /* check for index out of bound errors */
-        if (resultItems == null) {
-            Log.w(TAG, "getFolderItemsFilterAttr: resultItems is empty");
-            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
-            return;
-        }
-
-        FolderItemsData folderDataNative = new FolderItemsData(resultItems.size());
-
-        /* variables to accumulate attrs */
-        ArrayList<String> attrArray = new ArrayList<String>();
-        ArrayList<Integer> attrId = new ArrayList<Integer>();
-
-        for (int itemIndex = 0; itemIndex < resultItems.size(); itemIndex++) {
-            MediaSession.QueueItem item = resultItems.get(itemIndex);
-            // get the queue id
-            long qid = item.getQueueId();
-            byte[] uid = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
-
-            // get the array of uid from 2d to array 1D array
-            for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) {
-                folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx];
-            }
-
-            /* Set display name for current item */
-            folderDataNative.mDisplayNames[itemIndex] =
-                    getAttrValue(AvrcpConstants.ATTRID_TITLE, item, mediaController);
-
-            int maxAttributesRequested = 0;
-            boolean isAllAttribRequested = false;
-            /* check if remote requested for attributes */
-            if (folderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
-                int attrCnt = 0;
-
-                /* add requested attr ids to a temp array */
-                if (folderItemsReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
-                    isAllAttribRequested = true;
-                    maxAttributesRequested = AvrcpConstants.MAX_NUM_ATTR;
-                } else {
-                    /* get only the requested attribute ids from the request */
-                    maxAttributesRequested = folderItemsReqObj.mNumAttr;
-                }
-
-                /* lookup and copy values of attributes for ids requested above */
-                for (int idx = 0; idx < maxAttributesRequested; idx++) {
-                    /* check if media player provided requested attributes */
-                    String value = null;
-
-                    int attribId =
-                            isAllAttribRequested ? (idx + 1) : folderItemsReqObj.mAttrIDs[idx];
-                    value = getAttrValue(attribId, item, mediaController);
-                    if (value != null) {
-                        attrArray.add(value);
-                        attrId.add(attribId);
-                        attrCnt++;
-                    }
-                }
-                /* add num attr actually received from media player for a particular item */
-                folderDataNative.mAttributesNum[itemIndex] = attrCnt;
-            }
-        }
-
-        /* copy filtered attr ids and attr values to response parameters */
-        if (folderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
-            folderDataNative.mAttrIds = new int[attrId.size()];
-            for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) {
-                folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex);
-            }
-            folderDataNative.mAttrValues = attrArray.toArray(new String[attrArray.size()]);
-        }
-        for (int attrIndex = 0; attrIndex < folderDataNative.mAttributesNum.length; attrIndex++) {
-            if (DEBUG) {
-                Log.d(TAG, "folderDataNative.mAttributesNum"
-                        + folderDataNative.mAttributesNum[attrIndex] + " attrIndex " + attrIndex);
-            }
-        }
-
-        /* create rsp object and send response to remote device */
-        FolderItemsRsp rspObj =
-                new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR, Avrcp.sUIDCounter, scope,
-                        folderDataNative.mNumItems, folderDataNative.mFolderTypes,
-                        folderDataNative.mPlayable, folderDataNative.mItemTypes,
-                        folderDataNative.mItemUid, folderDataNative.mDisplayNames,
-                        folderDataNative.mAttributesNum, folderDataNative.mAttrIds,
-                        folderDataNative.mAttrValues);
-        mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
-    }
-
-    private String getAttrValue(int attr, MediaSession.QueueItem item,
-            @Nullable MediaController mediaController) {
-        String attrValue = null;
-        if (item == null) {
-            if (DEBUG) {
-                Log.d(TAG, "getAttrValue received null item");
-            }
-            return null;
-        }
-        try {
-            MediaDescription desc = item.getDescription();
-            Bundle extras = desc.getExtras();
-            boolean isCurrentTrack = item.getQueueId() == getActiveQueueItemId(mediaController);
-            MediaMetadata data = null;
-            if (isCurrentTrack) {
-                if (DEBUG) {
-                    Log.d(TAG, "getAttrValue: item is active, using current data");
-                }
-                data = mediaController.getMetadata();
-                if (data == null) {
-                    Log.e(TAG, "getMetadata didn't give us any metadata for the current track");
-                }
-            }
-
-            if (data == null) {
-                // TODO: This code can be removed when b/63117921 is resolved
-                data = (MediaMetadata) extras.get(GPM_BUNDLE_METADATA_KEY);
-                extras = null; // We no longer need the data in here
-            }
-
-            extras = fillBundle(data, extras);
-
-            if (DEBUG) {
-                Log.d(TAG, "getAttrValue: item " + item + " : " + desc);
-            }
-            switch (attr) {
-                case AvrcpConstants.ATTRID_TITLE:
-                    /* Title is mandatory attribute */
-                    if (isCurrentTrack) {
-                        attrValue = extras.getString(MediaMetadata.METADATA_KEY_TITLE);
-                    } else {
-                        attrValue = desc.getTitle().toString();
-                    }
-                    break;
-
-                case AvrcpConstants.ATTRID_ARTIST:
-                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_ARTIST);
-                    break;
-
-                case AvrcpConstants.ATTRID_ALBUM:
-                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_ALBUM);
-                    break;
-
-                case AvrcpConstants.ATTRID_TRACK_NUM:
-                    attrValue =
-                            Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
-                    break;
-
-                case AvrcpConstants.ATTRID_NUM_TRACKS:
-                    attrValue =
-                            Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
-                    break;
-
-                case AvrcpConstants.ATTRID_GENRE:
-                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_GENRE);
-                    break;
-
-                case AvrcpConstants.ATTRID_PLAY_TIME:
-                    attrValue = Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_DURATION));
-                    break;
-
-                case AvrcpConstants.ATTRID_COVER_ART:
-                    Log.e(TAG, "getAttrValue: Cover art attribute not supported");
-                    return null;
-
-                default:
-                    Log.e(TAG, "getAttrValue: Unknown attribute ID requested: " + attr);
-                    return null;
-            }
-        } catch (NullPointerException ex) {
-            Log.w(TAG, "getAttrValue: attr id not found in result");
-            /* checking if attribute is title, then it is mandatory and cannot send null */
-            if (attr == AvrcpConstants.ATTRID_TITLE) {
-                attrValue = "<Unknown Title>";
-            } else {
-                return null;
-            }
-        }
-        if (DEBUG) {
-            Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id:" + attr);
-        }
-        return attrValue;
-    }
-
-    private void getItemAttrFilterAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd mItemAttrReqObj,
-            MediaSession.QueueItem mediaItem, @Nullable MediaController mediaController) {
-        /* Response parameters */
-        int[] attrIds = null; /* array of attr ids */
-        String[] attrValues = null; /* array of attr values */
-
-        /* variables to temperorily add attrs */
-        ArrayList<String> attrArray = new ArrayList<String>();
-        ArrayList<Integer> attrId = new ArrayList<Integer>();
-        ArrayList<Integer> attrTempId = new ArrayList<Integer>();
-
-        /* check if remote device has requested for attributes */
-        if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
-            if (mItemAttrReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
-                for (int idx = 1; idx < AvrcpConstants.MAX_NUM_ATTR; idx++) {
-                    attrTempId.add(idx); /* attr id 0x00 is unused */
-                }
-            } else {
-                /* get only the requested attribute ids from the request */
-                for (int idx = 0; idx < mItemAttrReqObj.mNumAttr; idx++) {
-                    if (DEBUG) {
-                        Log.d(TAG, "getItemAttrFilterAttr: attr id[" + idx + "] :"
-                                + mItemAttrReqObj.mAttrIDs[idx]);
-                    }
-                    attrTempId.add(mItemAttrReqObj.mAttrIDs[idx]);
-                }
-            }
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "getItemAttrFilterAttr: attr id list size:" + attrTempId.size());
-        }
-        /* lookup and copy values of attributes for ids requested above */
-        for (int idx = 0; idx < attrTempId.size(); idx++) {
-            /* check if media player provided requested attributes */
-            String value = getAttrValue(attrTempId.get(idx), mediaItem, mediaController);
-            if (value != null) {
-                attrArray.add(value);
-                attrId.add(attrTempId.get(idx));
-            }
-        }
-
-        /* copy filtered attr ids and attr values to response parameters */
-        if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
-            attrIds = new int[attrId.size()];
-
-            for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) {
-                attrIds[attrIndex] = attrId.get(attrIndex);
-            }
-
-            attrValues = attrArray.toArray(new String[attrId.size()]);
-
-            /* create rsp object and send response */
-            ItemAttrRsp rspObj = new ItemAttrRsp(AvrcpConstants.RSP_NO_ERROR, attrIds, attrValues);
-            mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
-            return;
-        }
-    }
-
-    private long getActiveQueueItemId(@Nullable MediaController controller) {
-        if (controller == null) {
-            return MediaSession.QueueItem.UNKNOWN_ID;
-        }
-        PlaybackState state = controller.getPlaybackState();
-        if (state == null || state.getState() == PlaybackState.STATE_BUFFERING
-                || state.getState() == PlaybackState.STATE_NONE) {
-            return MediaSession.QueueItem.UNKNOWN_ID;
-        }
-        long qid = state.getActiveQueueItemId();
-        if (qid != MediaSession.QueueItem.UNKNOWN_ID) {
-            return qid;
-        }
-        // Check if we're presenting a "one item queue"
-        if (controller.getMetadata() != null) {
-            return SINGLE_QID;
-        }
-        return MediaSession.QueueItem.UNKNOWN_ID;
-    }
-
-    String displayMediaItem(MediaSession.QueueItem item) {
-        StringBuilder sb = new StringBuilder();
-        sb.append("#");
-        sb.append(item.getQueueId());
-        sb.append(": ");
-        sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_TITLE, item, null)));
-        sb.append(" - ");
-        sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_ALBUM, item, null)));
-        sb.append(" by ");
-        sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_ARTIST, item, null)));
-        sb.append(" (");
-        sb.append(getAttrValue(AvrcpConstants.ATTRID_PLAY_TIME, item, null));
-        sb.append(" ");
-        sb.append(getAttrValue(AvrcpConstants.ATTRID_TRACK_NUM, item, null));
-        sb.append("/");
-        sb.append(getAttrValue(AvrcpConstants.ATTRID_NUM_TRACKS, item, null));
-        sb.append(") ");
-        sb.append(getAttrValue(AvrcpConstants.ATTRID_GENRE, item, null));
-        return sb.toString();
-    }
-
-    public void dump(StringBuilder sb, @Nullable MediaController mediaController) {
-        ProfileService.println(sb, "AddressedPlayer info:");
-        ProfileService.println(sb, "mLastTrackIdSent: " + mLastTrackIdSent);
-        ProfileService.println(sb, "mNowPlayingList: " + mNowPlayingList.size() + " elements");
-        long currentQueueId = getActiveQueueItemId(mediaController);
-        for (MediaSession.QueueItem item : mNowPlayingList) {
-            long itemId = item.getQueueId();
-            ProfileService.println(sb,
-                    (itemId == currentQueueId ? "*" : " ") + displayMediaItem(item));
-        }
-    }
-}
diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
deleted file mode 100644
index e30ad41..0000000
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ /dev/null
@@ -1,3123 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.avrcp;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAvrcp;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.media.AudioManager;
-import android.media.AudioPlaybackConfiguration;
-import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.os.UserManager;
-import android.util.Log;
-import android.view.KeyEvent;
-
-import com.android.bluetooth.R;
-import com.android.bluetooth.Utils;
-import com.android.bluetooth.btservice.ProfileService;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-/******************************************************************************
- * support Bluetooth AVRCP profile. support metadata, play status, event
- * notifications, address player selection and browse feature implementation.
- ******************************************************************************/
-
-public final class Avrcp {
-    private static final boolean DEBUG = false;
-    private static final String TAG = "Avrcp";
-    private static final String ABSOLUTE_VOLUME_BLACKLIST = "absolute_volume_blacklist";
-
-    private Context mContext;
-    private final AudioManager mAudioManager;
-    private volatile AvrcpMessageHandler mHandler;
-    private Handler mAudioManagerPlaybackHandler;
-    private AudioManagerPlaybackListener mAudioManagerPlaybackCb;
-    private MediaSessionManager mMediaSessionManager;
-    @Nullable private MediaController mMediaController;
-    private MediaControllerListener mMediaControllerCb;
-    private MediaAttributes mMediaAttributes;
-    private long mLastQueueId;
-    private PackageManager mPackageManager;
-    private int mTransportControlFlags;
-    @NonNull private PlaybackState mCurrentPlayState;
-    private int mA2dpState;
-    private boolean mAudioManagerIsPlaying;
-    private int mPlayStatusChangedNT;
-    private byte mReportedPlayStatus;
-    private int mTrackChangedNT;
-    private int mPlayPosChangedNT;
-    private int mAddrPlayerChangedNT;
-    private int mReportedPlayerID;
-    private int mNowPlayingListChangedNT;
-    private long mPlaybackIntervalMs;
-    private long mLastReportedPosition;
-    private long mNextPosMs;
-    private long mPrevPosMs;
-    private int mFeatures;
-    private int mRemoteVolume;
-    private int mLastRemoteVolume;
-    private int mInitialRemoteVolume;
-
-    /* Local volume in audio index 0-15 */
-    private int mLocalVolume;
-    private int mLastLocalVolume;
-    private int mAbsVolThreshold;
-
-    private String mAddress;
-    private HashMap<Integer, Integer> mVolumeMapping;
-
-    private int mLastDirection;
-    private final int mVolumeStep;
-    private final int mAudioStreamMax;
-    private boolean mVolCmdSetInProgress;
-    private int mAbsVolRetryTimes;
-
-    private static final int NO_PLAYER_ID = 0;
-
-    private int mCurrAddrPlayerID;
-    private int mCurrBrowsePlayerID;
-    private int mLastUsedPlayerID;
-    private AvrcpMediaRsp mAvrcpMediaRsp;
-
-    /* UID counter to be shared across different files. */
-    static short sUIDCounter = AvrcpConstants.DEFAULT_UID_COUNTER;
-
-    /* BTRC features */
-    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;
-
-    /* AVRC response codes, from avrc_defs */
-    private static final int AVRC_RSP_NOT_IMPL = 8;
-    private static final int AVRC_RSP_ACCEPT = 9;
-    private static final int AVRC_RSP_REJ = 10;
-    private static final int AVRC_RSP_IN_TRANS = 11;
-    private static final int AVRC_RSP_IMPL_STBL = 12;
-    private static final int AVRC_RSP_CHANGED = 13;
-    private static final int AVRC_RSP_INTERIM = 15;
-
-    /* AVRC request commands from Native */
-    private static final int MSG_NATIVE_REQ_GET_RC_FEATURES = 1;
-    private static final int MSG_NATIVE_REQ_GET_PLAY_STATUS = 2;
-    private static final int MSG_NATIVE_REQ_GET_ELEM_ATTRS = 3;
-    private static final int MSG_NATIVE_REQ_REGISTER_NOTIFICATION = 4;
-    private static final int MSG_NATIVE_REQ_VOLUME_CHANGE = 5;
-    private static final int MSG_NATIVE_REQ_GET_FOLDER_ITEMS = 6;
-    private static final int MSG_NATIVE_REQ_SET_ADDR_PLAYER = 7;
-    private static final int MSG_NATIVE_REQ_SET_BR_PLAYER = 8;
-    private static final int MSG_NATIVE_REQ_CHANGE_PATH = 9;
-    private static final int MSG_NATIVE_REQ_PLAY_ITEM = 10;
-    private static final int MSG_NATIVE_REQ_GET_ITEM_ATTR = 11;
-    private static final int MSG_NATIVE_REQ_GET_TOTAL_NUM_OF_ITEMS = 12;
-    private static final int MSG_NATIVE_REQ_PASS_THROUGH = 13;
-
-    /* other AVRC messages */
-    private static final int MSG_PLAY_INTERVAL_TIMEOUT = 14;
-    private static final int MSG_SET_ABSOLUTE_VOLUME = 16;
-    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 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;
-
-    /* Communicates with MediaPlayer to fetch media content */
-    private BrowsedMediaPlayer mBrowsedMediaPlayer;
-
-    /* Addressed player handling */
-    private AddressedMediaPlayer mAddressedMediaPlayer;
-
-    /* List of Media player instances, useful for retrieving MediaPlayerList or MediaPlayerInfo */
-    private SortedMap<Integer, MediaPlayerInfo> mMediaPlayerInfoList;
-    private boolean mAvailablePlayerViewChanged;
-
-    /* List of media players which supports browse */
-    private List<BrowsePlayerInfo> mBrowsePlayerInfoList;
-
-    /* Manage browsed players */
-    private AvrcpBrowseManager mAvrcpBrowseManager;
-
-    /* Broadcast receiver for device connections intent broadcasts */
-    private final BroadcastReceiver mAvrcpReceiver = new AvrcpServiceBroadcastReceiver();
-    private final BroadcastReceiver mBootReceiver = new AvrcpServiceBootReceiver();
-
-    /* Recording passthrough key dispatches */
-    private static final int PASSTHROUGH_LOG_MAX_SIZE = DEBUG ? 50 : 10;
-    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 class MediaKeyLog {
-        private long mTimeSent;
-        private long mTimeProcessed;
-        private String mPackage;
-        private KeyEvent mEvent;
-
-        MediaKeyLog(long time, KeyEvent event) {
-            mEvent = event;
-            mTimeSent = time;
-        }
-
-        public boolean addDispatch(long time, KeyEvent event, String packageName) {
-            if (mPackage != null) {
-                return false;
-            }
-            if (event.getAction() != mEvent.getAction()) {
-                return false;
-            }
-            if (event.getKeyCode() != mEvent.getKeyCode()) {
-                return false;
-            }
-            mPackage = packageName;
-            mTimeProcessed = time;
-            return true;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder();
-            sb.append(android.text.format.DateFormat.format("MM-dd HH:mm:ss", mTimeSent));
-            sb.append(" " + mEvent.toString());
-            if (mPackage == null) {
-                sb.append(" (undispatched)");
-            } else {
-                sb.append(" to " + mPackage);
-                sb.append(" in " + (mTimeProcessed - mTimeSent) + "ms");
-            }
-            return sb.toString();
-        }
-    }
-
-    static {
-        classInitNative();
-    }
-
-    private Avrcp(Context context) {
-        mMediaAttributes = new MediaAttributes(null);
-        mLastQueueId = MediaSession.QueueItem.UNKNOWN_ID;
-        mCurrentPlayState =
-                new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, -1L, 0.0f).build();
-        mReportedPlayStatus = PLAYSTATUS_ERROR;
-        mA2dpState = BluetoothA2dp.STATE_NOT_PLAYING;
-        mAudioManagerIsPlaying = false;
-        mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-        mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-        mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-        mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-        mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-        mPlaybackIntervalMs = 0L;
-        mLastReportedPosition = -1;
-        mNextPosMs = -1;
-        mPrevPosMs = -1;
-        mFeatures = 0;
-        mRemoteVolume = -1;
-        mInitialRemoteVolume = -1;
-        mLastRemoteVolume = -1;
-        mLastDirection = 0;
-        mVolCmdSetInProgress = false;
-        mAbsVolRetryTimes = 0;
-        mLocalVolume = -1;
-        mLastLocalVolume = -1;
-        mAbsVolThreshold = 0;
-        mVolumeMapping = new HashMap<Integer, Integer>();
-        mCurrAddrPlayerID = NO_PLAYER_ID;
-        mReportedPlayerID = mCurrAddrPlayerID;
-        mCurrBrowsePlayerID = 0;
-        mContext = context;
-        mLastUsedPlayerID = 0;
-        mAddressedMediaPlayer = null;
-
-        initNative();
-
-        mMediaSessionManager =
-                (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
-        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        mAudioStreamMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        mVolumeStep = Math.max(AVRCP_BASE_VOLUME_STEP, AVRCP_MAX_VOL / mAudioStreamMax);
-
-        Resources resources = context.getResources();
-        if (resources != null) {
-            mAbsVolThreshold =
-                    resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold);
-
-            // Update the threshold if the thresholdPercent is valid
-            int thresholdPercent =
-                    resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold_percent);
-            if (thresholdPercent >= 0 && thresholdPercent <= 100) {
-                mAbsVolThreshold = (thresholdPercent * mAudioStreamMax) / 100;
-            }
-        }
-
-        // Register for package removal intent broadcasts for media button receiver persistence
-        IntentFilter pkgFilter = new IntentFilter();
-        pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-        pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        pkgFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
-        pkgFilter.addDataScheme("package");
-        context.registerReceiver(mAvrcpReceiver, pkgFilter);
-
-        IntentFilter bootFilter = new IntentFilter();
-        bootFilter.addAction(Intent.ACTION_USER_UNLOCKED);
-        context.registerReceiver(mBootReceiver, bootFilter);
-    }
-
-    private synchronized void start() {
-        HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
-        thread.start();
-        Looper looper = thread.getLooper();
-        mHandler = new AvrcpMessageHandler(looper);
-        mAudioManagerPlaybackHandler = new Handler(looper);
-        mAudioManagerPlaybackCb = new AudioManagerPlaybackListener();
-        mMediaControllerCb = new MediaControllerListener();
-        mAvrcpMediaRsp = new AvrcpMediaRsp();
-        mMediaPlayerInfoList = new TreeMap<Integer, MediaPlayerInfo>();
-        mAvailablePlayerViewChanged = false;
-        mBrowsePlayerInfoList = Collections.synchronizedList(new ArrayList<BrowsePlayerInfo>());
-        mPassthroughDispatched = 0;
-        mPassthroughLogs = new EvictingQueue<MediaKeyLog>(PASSTHROUGH_LOG_MAX_SIZE);
-        mPassthroughPending = Collections.synchronizedList(new ArrayList<MediaKeyLog>());
-        if (mMediaSessionManager != null) {
-            mMediaSessionManager.addOnActiveSessionsChangedListener(mActiveSessionListener, null,
-                    mHandler);
-            mMediaSessionManager.setCallback(mButtonDispatchCallback, null);
-        }
-        mPackageManager = mContext.getApplicationContext().getPackageManager();
-
-        /* create object to communicate with addressed player */
-        mAddressedMediaPlayer = new AddressedMediaPlayer(mAvrcpMediaRsp);
-
-        /* initialize BrowseMananger which manages Browse commands and response */
-        mAvrcpBrowseManager = new AvrcpBrowseManager(mContext, mAvrcpMediaRsp);
-
-        initMediaPlayersList();
-
-        UserManager manager = UserManager.get(mContext);
-        if (manager == null || manager.isUserUnlocked()) {
-            if (DEBUG) {
-                Log.d(TAG, "User already unlocked, initializing player lists");
-            }
-            // initialize browsable player list and build media player list
-            buildBrowsablePlayerList();
-        }
-
-        mAudioManager.registerAudioPlaybackCallback(mAudioManagerPlaybackCb,
-                mAudioManagerPlaybackHandler);
-    }
-
-    public static Avrcp make(Context context) {
-        if (DEBUG) {
-            Log.v(TAG, "make");
-        }
-        Avrcp ar = new Avrcp(context);
-        ar.start();
-        return ar;
-    }
-
-    public synchronized void doQuit() {
-        if (DEBUG) {
-            Log.d(TAG, "doQuit");
-        }
-        if (mAudioManager != null) {
-            mAudioManager.unregisterAudioPlaybackCallback(mAudioManagerPlaybackCb);
-        }
-        if (mMediaController != null) {
-            mMediaController.unregisterCallback(mMediaControllerCb);
-        }
-        if (mMediaSessionManager != null) {
-            mMediaSessionManager.setCallback(null, null);
-            mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveSessionListener);
-        }
-
-        mAudioManagerPlaybackHandler.removeCallbacksAndMessages(null);
-        mHandler.removeCallbacksAndMessages(null);
-        Looper looper = mHandler.getLooper();
-        mHandler = null;
-        if (looper != null) {
-            looper.quitSafely();
-        }
-
-        mAudioManagerPlaybackHandler = null;
-        mContext.unregisterReceiver(mAvrcpReceiver);
-        mContext.unregisterReceiver(mBootReceiver);
-
-        mAddressedMediaPlayer.cleanup();
-        mAvrcpBrowseManager.cleanup();
-    }
-
-    public void cleanup() {
-        if (DEBUG) {
-            Log.d(TAG, "cleanup");
-        }
-        cleanupNative();
-        if (mVolumeMapping != null) {
-            mVolumeMapping.clear();
-        }
-    }
-
-    private class AudioManagerPlaybackListener extends AudioManager.AudioPlaybackCallback {
-        @Override
-        public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
-            super.onPlaybackConfigChanged(configs);
-            boolean isPlaying = false;
-            for (AudioPlaybackConfiguration config : configs) {
-                if (DEBUG) {
-                    Log.d(TAG, "AudioManager Player: "
-                            + AudioPlaybackConfiguration.toLogFriendlyString(config));
-                }
-                if (config.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                    isPlaying = true;
-                    break;
-                }
-            }
-            if (DEBUG) {
-                Log.d(TAG, "AudioManager isPlaying: " + isPlaying);
-            }
-            if (mAudioManagerIsPlaying != isPlaying) {
-                mAudioManagerIsPlaying = isPlaying;
-                updateCurrentMediaState();
-            }
-        }
-    }
-
-    private class MediaControllerListener extends MediaController.Callback {
-        @Override
-        public void onMetadataChanged(MediaMetadata metadata) {
-            if (DEBUG) {
-                Log.v(TAG, "onMetadataChanged");
-            }
-            updateCurrentMediaState();
-        }
-
-        @Override
-        public synchronized void onPlaybackStateChanged(PlaybackState state) {
-            if (DEBUG) {
-                Log.v(TAG, "onPlaybackStateChanged: state " + state.toString());
-            }
-
-            updateCurrentMediaState();
-        }
-
-        @Override
-        public void onSessionDestroyed() {
-            Log.v(TAG, "MediaController session destroyed");
-            synchronized (Avrcp.this) {
-                if (mMediaController != null) {
-                    removeMediaController(mMediaController.getWrappedInstance());
-                }
-            }
-        }
-
-        @Override
-        public void onQueueChanged(List<MediaSession.QueueItem> queue) {
-            if (queue == null) {
-                Log.v(TAG, "onQueueChanged: received null queue");
-                return;
-            }
-
-            final AvrcpMessageHandler handler = mHandler;
-            if (handler == null) {
-                if (DEBUG) Log.d(TAG, "onQueueChanged: mHandler is already null");
-                return;
-            }
-
-            Log.v(TAG, "onQueueChanged: NowPlaying list changed, Queue Size = "
-                    + queue.size());
-            handler.sendEmptyMessage(MSG_NOW_PLAYING_CHANGED_RSP);
-        }
-    }
-
-    /** Handles Avrcp messages. */
-    private final class AvrcpMessageHandler extends Handler {
-        private AvrcpMessageHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_NATIVE_REQ_GET_RC_FEATURES: {
-                    String address = (String) msg.obj;
-                    mFeatures = msg.arg1;
-                    mFeatures = modifyRcFeatureFromBlacklist(mFeatures, address);
-                    if (DEBUG) {
-                        Log.v(TAG,
-                                "MSG_NATIVE_REQ_GET_RC_FEATURES: address=" + address + ", features="
-                                        + msg.arg1 + ", mFeatures=" + mFeatures);
-                    }
-                    mAudioManager.avrcpSupportsAbsoluteVolume(address, isAbsoluteVolumeSupported());
-                    mLastLocalVolume = -1;
-                    mRemoteVolume = -1;
-                    mLocalVolume = -1;
-                    mInitialRemoteVolume = -1;
-                    mAddress = address;
-                    if (mVolumeMapping != null) {
-                        mVolumeMapping.clear();
-                    }
-                    break;
-                }
-
-                case MSG_NATIVE_REQ_GET_PLAY_STATUS: {
-                    byte[] address = (byte[]) msg.obj;
-                    int btstate = getBluetoothPlayState(mCurrentPlayState);
-                    int length = (int) mMediaAttributes.getLength();
-                    int position = (int) getPlayPosition();
-                    if (DEBUG) {
-                        Log.v(TAG,
-                                "MSG_NATIVE_REQ_GET_PLAY_STATUS, responding with state " + btstate
-                                        + " len " + length + " pos " + position);
-                    }
-                    getPlayStatusRspNative(address, btstate, length, position);
-                    break;
-                }
-
-                case MSG_NATIVE_REQ_GET_ELEM_ATTRS: {
-                    String[] textArray;
-                    AvrcpCmd.ElementAttrCmd elem = (AvrcpCmd.ElementAttrCmd) msg.obj;
-                    byte numAttr = elem.mNumAttr;
-                    int[] attrIds = elem.mAttrIDs;
-                    if (DEBUG) {
-                        Log.v(TAG, "MSG_NATIVE_REQ_GET_ELEM_ATTRS:numAttr=" + numAttr);
-                    }
-                    textArray = new String[numAttr];
-                    StringBuilder responseDebug = new StringBuilder();
-                    responseDebug.append("getElementAttr response: ");
-                    for (int i = 0; i < numAttr; ++i) {
-                        textArray[i] = mMediaAttributes.getString(attrIds[i]);
-                        responseDebug.append("[" + attrIds[i] + "=");
-                        if (attrIds[i] == AvrcpConstants.ATTRID_TITLE
-                                || attrIds[i] == AvrcpConstants.ATTRID_ARTIST
-                                || attrIds[i] == AvrcpConstants.ATTRID_ALBUM) {
-                            responseDebug.append(Utils.ellipsize(textArray[i]) + "] ");
-                        } else {
-                            responseDebug.append(textArray[i] + "] ");
-                        }
-                    }
-                    Log.v(TAG, responseDebug.toString());
-                    byte[] bdaddr = elem.mAddress;
-                    getElementAttrRspNative(bdaddr, numAttr, attrIds, textArray);
-                    break;
-                }
-
-                case MSG_NATIVE_REQ_REGISTER_NOTIFICATION:
-                    if (DEBUG) {
-                        Log.v(TAG,
-                                "MSG_NATIVE_REQ_REGISTER_NOTIFICATION:event=" + msg.arg1 + " param="
-                                        + msg.arg2);
-                    }
-                    processRegisterNotification((byte[]) msg.obj, msg.arg1, msg.arg2);
-                    break;
-
-                case MSG_NOW_PLAYING_CHANGED_RSP:
-                    if (DEBUG) {
-                        Log.v(TAG, "MSG_NOW_PLAYING_CHANGED_RSP");
-                    }
-                    removeMessages(MSG_NOW_PLAYING_CHANGED_RSP);
-                    updateCurrentMediaState();
-                    break;
-
-                case MSG_PLAY_INTERVAL_TIMEOUT:
-                    sendPlayPosNotificationRsp(false);
-                    break;
-
-                case MSG_NATIVE_REQ_VOLUME_CHANGE:
-                    if (!isAbsoluteVolumeSupported()) {
-                        if (DEBUG) {
-                            Log.v(TAG, "MSG_NATIVE_REQ_VOLUME_CHANGE ignored, not supported");
-                        }
-                        break;
-                    }
-                    byte absVol = (byte) ((byte) msg.arg1 & 0x7f); // discard MSB as it is RFD
-                    if (DEBUG) {
-                        Log.v(TAG, "MSG_NATIVE_REQ_VOLUME_CHANGE: volume=" + absVol + " ctype="
-                                + msg.arg2);
-                    }
-
-                    if (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_REJ) {
-                        if (!mVolCmdSetInProgress) {
-                            Log.e(TAG, "Unsolicited response, ignored");
-                            break;
-                        }
-                        removeMessages(MSG_ABS_VOL_TIMEOUT);
-
-                        mVolCmdSetInProgress = false;
-                        mAbsVolRetryTimes = 0;
-                    }
-
-                    // convert remote volume to local volume
-                    int volIndex = convertToAudioStreamVolume(absVol);
-                    if (mInitialRemoteVolume == -1) {
-                        mInitialRemoteVolume = absVol;
-                        if (mAbsVolThreshold > 0 && mAbsVolThreshold < mAudioStreamMax
-                                && volIndex > mAbsVolThreshold) {
-                            if (DEBUG) {
-                                Log.v(TAG, "remote inital volume too high " + volIndex + ">"
-                                        + mAbsVolThreshold);
-                            }
-                            Message msg1 = this.obtainMessage(MSG_SET_ABSOLUTE_VOLUME,
-                                    mAbsVolThreshold, 0);
-                            this.sendMessage(msg1);
-                            mRemoteVolume = absVol;
-                            mLocalVolume = volIndex;
-                            break;
-                        }
-                    }
-
-                    if (mLocalVolume != volIndex && (msg.arg2 == AVRC_RSP_ACCEPT
-                            || msg.arg2 == AVRC_RSP_CHANGED || msg.arg2 == AVRC_RSP_INTERIM)) {
-                    /* If the volume has successfully changed */
-                        mLocalVolume = volIndex;
-                        if (mLastLocalVolume != -1 && msg.arg2 == AVRC_RSP_ACCEPT) {
-                            if (mLastLocalVolume != volIndex) {
-                            /* remote volume changed more than requested due to
-                             * local and remote has different volume steps */
-                                if (DEBUG) {
-                                    Log.d(TAG,
-                                            "Remote returned volume does not match desired volume "
-                                                    + mLastLocalVolume + " vs " + volIndex);
-                                }
-                                mLastLocalVolume = mLocalVolume;
-                            }
-                        }
-
-                        notifyVolumeChanged(mLocalVolume);
-                        mRemoteVolume = absVol;
-                        long pecentVolChanged = ((long) absVol * 100) / 0x7f;
-                        Log.e(TAG, "percent volume changed: " + pecentVolChanged + "%");
-                    } else if (msg.arg2 == AVRC_RSP_REJ) {
-                        Log.e(TAG, "setAbsoluteVolume call rejected");
-                    }
-                    break;
-
-                case MSG_SET_ABSOLUTE_VOLUME:
-                    if (!isAbsoluteVolumeSupported()) {
-                        if (DEBUG) {
-                            Log.v(TAG, "ignore MSG_SET_ABSOLUTE_VOLUME");
-                        }
-                        break;
-                    }
-
-                    if (DEBUG) {
-                        Log.v(TAG, "MSG_SET_ABSOLUTE_VOLUME");
-                    }
-
-                    if (mVolCmdSetInProgress) {
-                        if (DEBUG) {
-                            Log.w(TAG, "There is already a volume command in progress.");
-                        }
-                        break;
-                    }
-
-                    // Remote device didn't set initial volume. Let's black list it
-                    if (mInitialRemoteVolume == -1) {
-                        if (DEBUG) {
-                            Log.d(TAG, "remote " + mAddress
-                                    + " never tell us initial volume, black list it.");
-                        }
-                        blackListCurrentDevice("MSG_SET_ABSOLUTE_VOLUME");
-                        break;
-                    }
-
-                    int avrcpVolume = convertToAvrcpVolume(msg.arg1);
-                    avrcpVolume = Math.min(AVRCP_MAX_VOL, Math.max(0, avrcpVolume));
-                    if (DEBUG) {
-                        Log.d(TAG, "Setting volume to " + msg.arg1 + "-" + avrcpVolume);
-                    }
-                    if (setVolumeNative(avrcpVolume)) {
-                        sendMessageDelayed(obtainMessage(MSG_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY);
-                        mVolCmdSetInProgress = true;
-                        mLastRemoteVolume = avrcpVolume;
-                        mLastLocalVolume = msg.arg1;
-                    } else {
-                        if (DEBUG) {
-                            Log.d(TAG, "setVolumeNative failed");
-                        }
-                    }
-                    break;
-
-                case MSG_ABS_VOL_TIMEOUT:
-                    if (DEBUG) {
-                        Log.v(TAG, "MSG_ABS_VOL_TIMEOUT: Volume change cmd timed out.");
-                    }
-                    mVolCmdSetInProgress = false;
-                    if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) {
-                        mAbsVolRetryTimes = 0;
-                    /* too many volume change failures, black list the device */
-                        blackListCurrentDevice("MSG_ABS_VOL_TIMEOUT");
-                    } else {
-                        mAbsVolRetryTimes += 1;
-                        if (setVolumeNative(mLastRemoteVolume)) {
-                            sendMessageDelayed(obtainMessage(MSG_ABS_VOL_TIMEOUT),
-                                    CMD_TIMEOUT_DELAY);
-                            mVolCmdSetInProgress = true;
-                        }
-                    }
-                    break;
-
-                case MSG_SET_A2DP_AUDIO_STATE:
-                    if (DEBUG) {
-                        Log.v(TAG, "MSG_SET_A2DP_AUDIO_STATE:" + msg.arg1);
-                    }
-                    mA2dpState = msg.arg1;
-                    updateCurrentMediaState();
-                    break;
-
-                case MSG_NATIVE_REQ_GET_FOLDER_ITEMS: {
-                    AvrcpCmd.FolderItemsCmd folderObj = (AvrcpCmd.FolderItemsCmd) msg.obj;
-                    if (DEBUG) {
-                        Log.v(TAG, "MSG_NATIVE_REQ_GET_FOLDER_ITEMS " + folderObj);
-                    }
-                    switch (folderObj.mScope) {
-                        case AvrcpConstants.BTRC_SCOPE_PLAYER_LIST:
-                            handleMediaPlayerListRsp(folderObj);
-                            break;
-                        case AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM:
-                        case AvrcpConstants.BTRC_SCOPE_NOW_PLAYING:
-                            handleGetFolderItemBrowseResponse(folderObj, folderObj.mAddress);
-                            break;
-                        default:
-                            Log.e(TAG, "unknown scope for getfolderitems. scope = "
-                                    + folderObj.mScope);
-                            getFolderItemsRspNative(folderObj.mAddress,
-                                    AvrcpConstants.RSP_INV_SCOPE, (short) 0, (byte) 0, 0, null,
-                                    null, null, null, null, null, null, null);
-                    }
-                    break;
-                }
-
-                case MSG_NATIVE_REQ_SET_ADDR_PLAYER:
-                    // object is bdaddr, argument 1 is the selected player id
-                    if (DEBUG) {
-                        Log.v(TAG, "MSG_NATIVE_REQ_SET_ADDR_PLAYER id=" + msg.arg1);
-                    }
-                    setAddressedPlayer((byte[]) msg.obj, msg.arg1);
-                    break;
-
-                case MSG_NATIVE_REQ_GET_ITEM_ATTR:
-                    // msg object contains the item attribute object
-                    AvrcpCmd.ItemAttrCmd cmd = (AvrcpCmd.ItemAttrCmd) msg.obj;
-                    if (DEBUG) {
-                        Log.v(TAG, "MSG_NATIVE_REQ_GET_ITEM_ATTR " + cmd);
-                    }
-                    handleGetItemAttr(cmd);
-                    break;
-
-                case MSG_NATIVE_REQ_SET_BR_PLAYER:
-                    // argument 1 is the selected player id
-                    if (DEBUG) {
-                        Log.v(TAG, "MSG_NATIVE_REQ_SET_BR_PLAYER id=" + msg.arg1);
-                    }
-                    setBrowsedPlayer((byte[]) msg.obj, msg.arg1);
-                    break;
-
-                case MSG_NATIVE_REQ_CHANGE_PATH: {
-                    if (DEBUG) {
-                        Log.v(TAG, "MSG_NATIVE_REQ_CHANGE_PATH");
-                    }
-                    Bundle data = msg.getData();
-                    byte[] bdaddr = data.getByteArray("BdAddress");
-                    byte[] folderUid = data.getByteArray("folderUid");
-                    byte direction = data.getByte("direction");
-                    if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) != null) {
-                        mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr)
-                                .changePath(folderUid, direction);
-                    } else {
-                        Log.e(TAG, "Remote requesting change path before setbrowsedplayer");
-                        changePathRspNative(bdaddr, AvrcpConstants.RSP_BAD_CMD, 0);
-                    }
-                    break;
-                }
-
-                case MSG_NATIVE_REQ_PLAY_ITEM: {
-                    Bundle data = msg.getData();
-                    byte[] bdaddr = data.getByteArray("BdAddress");
-                    byte[] uid = data.getByteArray("uid");
-                    byte scope = data.getByte("scope");
-                    if (DEBUG) {
-                        Log.v(TAG, "MSG_NATIVE_REQ_PLAY_ITEM scope=" + scope + " id="
-                                + Utils.byteArrayToString(uid));
-                    }
-                    handlePlayItemResponse(bdaddr, uid, scope);
-                    break;
-                }
-
-                case MSG_NATIVE_REQ_GET_TOTAL_NUM_OF_ITEMS:
-                    if (DEBUG) {
-                        Log.v(TAG, "MSG_NATIVE_REQ_GET_TOTAL_NUM_OF_ITEMS scope=" + msg.arg1);
-                    }
-                    // argument 1 is scope, object is bdaddr
-                    handleGetTotalNumOfItemsResponse((byte[]) msg.obj, (byte) msg.arg1);
-                    break;
-
-                case MSG_NATIVE_REQ_PASS_THROUGH:
-                    if (DEBUG) {
-                        Log.v(TAG,
-                                "MSG_NATIVE_REQ_PASS_THROUGH: id=" + msg.arg1 + " st=" + msg.arg2);
-                    }
-                    // argument 1 is id, argument 2 is keyState
-                    handlePassthroughCmd(msg.arg1, msg.arg2);
-                    break;
-
-                default:
-                    Log.e(TAG, "unknown message! msg.what=" + msg.what);
-                    break;
-            }
-        }
-    }
-
-    private PlaybackState updatePlaybackState() {
-        PlaybackState newState = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE,
-                PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f).build();
-        synchronized (this) {
-            PlaybackState controllerState = null;
-            if (mMediaController != null) {
-                controllerState = mMediaController.getPlaybackState();
-            }
-
-            if (controllerState != null) {
-                newState = controllerState;
-            }
-            // Use the AudioManager to update the playback state.
-            // NOTE: We cannot use the
-            //    (mA2dpState == BluetoothA2dp.STATE_PLAYING)
-            // check, because after Pause, the A2DP state remains in
-            // STATE_PLAYING for 3 more seconds.
-            // As a result of that, if we pause the music, on carkits the
-            // Play status indicator will continue to display "Playing"
-            // for 3 more seconds which can be confusing.
-            if ((mAudioManagerIsPlaying && newState.getState() != PlaybackState.STATE_PLAYING) || (
-                    controllerState == null && mAudioManager != null
-                            && mAudioManager.isMusicActive())) {
-                // Use AudioManager playback state if we don't have the state
-                // from MediaControlller
-                PlaybackState.Builder builder = new PlaybackState.Builder();
-                if (mAudioManagerIsPlaying) {
-                    builder.setState(PlaybackState.STATE_PLAYING,
-                            PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f);
-                } else {
-                    builder.setState(PlaybackState.STATE_PAUSED,
-                            PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f);
-                }
-                newState = builder.build();
-            }
-        }
-
-        byte newPlayStatus = getBluetoothPlayState(newState);
-
-        /* update play status in global media player list */
-        MediaPlayerInfo player = getAddressedPlayerInfo();
-        if (player != null) {
-            player.setPlayStatus(newPlayStatus);
-        }
-
-        if (DEBUG) {
-            Log.v(TAG, "updatePlaybackState (" + mPlayStatusChangedNT + "): " + mReportedPlayStatus
-                    + "➡" + newPlayStatus + "(" + newState + ")");
-        }
-
-        if (newState != null) {
-            mCurrentPlayState = newState;
-        }
-
-        return mCurrentPlayState;
-    }
-
-    private void sendPlaybackStatus(int playStatusChangedNT, byte playbackState) {
-        registerNotificationRspPlayStatusNative(playStatusChangedNT, playbackState);
-        mPlayStatusChangedNT = playStatusChangedNT;
-        mReportedPlayStatus = playbackState;
-    }
-
-    private void updateTransportControls(int transportControlFlags) {
-        mTransportControlFlags = transportControlFlags;
-    }
-
-    class MediaAttributes {
-        private boolean mExists;
-        private String mTitle;
-        private String mArtistName;
-        private String mAlbumName;
-        private String mMediaNumber;
-        private String mMediaTotalNumber;
-        private String mGenre;
-        private long mPlayingTimeMs;
-
-        private static final int ATTR_TITLE = 1;
-        private static final int ATTR_ARTIST_NAME = 2;
-        private static final int ATTR_ALBUM_NAME = 3;
-        private static final int ATTR_MEDIA_NUMBER = 4;
-        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;
-
-
-        MediaAttributes(MediaMetadata data) {
-            mExists = data != null;
-            if (!mExists) {
-                return;
-            }
-
-            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));
-            mMediaTotalNumber =
-                    longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
-            mGenre = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_GENRE));
-            mPlayingTimeMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION);
-
-            // Try harder for the title.
-            mTitle = data.getString(MediaMetadata.METADATA_KEY_TITLE);
-
-            if (mTitle == null) {
-                MediaDescription desc = data.getDescription();
-                if (desc != null) {
-                    CharSequence val = desc.getDescription();
-                    if (val != null) {
-                        mTitle = val.toString();
-                    }
-                }
-            }
-
-            if (mTitle == null) {
-                mTitle = new String();
-            }
-        }
-
-        public long getLength() {
-            if (!mExists) {
-                return 0L;
-            }
-            return mPlayingTimeMs;
-        }
-
-        public boolean equals(MediaAttributes other) {
-            if (other == null) {
-                return false;
-            }
-
-            if (mExists != other.mExists) {
-                return false;
-            }
-
-            if (!mExists) {
-                return true;
-            }
-
-            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);
-        }
-
-        public String getString(int attrId) {
-            if (!mExists) {
-                return new String();
-            }
-
-            switch (attrId) {
-                case ATTR_TITLE:
-                    return mTitle;
-                case ATTR_ARTIST_NAME:
-                    return mArtistName;
-                case ATTR_ALBUM_NAME:
-                    return mAlbumName;
-                case ATTR_MEDIA_NUMBER:
-                    return mMediaNumber;
-                case ATTR_MEDIA_TOTAL_NUMBER:
-                    return mMediaTotalNumber;
-                case ATTR_GENRE:
-                    return mGenre;
-                case ATTR_PLAYING_TIME_MS:
-                    return Long.toString(mPlayingTimeMs);
-                default:
-                    return new String();
-            }
-        }
-
-        private String stringOrBlank(String s) {
-            return s == null ? new String() : s;
-        }
-
-        private String longStringOrBlank(Long s) {
-            return s == null ? new String() : s.toString();
-        }
-
-        @Override
-        public String toString() {
-            if (!mExists) {
-                return "[MediaAttributes: none]";
-            }
-
-            return "[MediaAttributes: " + mTitle + " - " + mAlbumName + " by " + mArtistName + " ("
-                    + mPlayingTimeMs + " " + mMediaNumber + "/" + mMediaTotalNumber + ") " + mGenre
-                    + "]";
-        }
-
-        public String toRedactedString() {
-            if (!mExists) {
-                return "[MediaAttributes: none]";
-            }
-
-            return "[MediaAttributes: " + Utils.ellipsize(mTitle) + " - " + Utils.ellipsize(
-                    mAlbumName) + " by " + Utils.ellipsize(mArtistName) + " (" + mPlayingTimeMs
-                    + " " + mMediaNumber + "/" + mMediaTotalNumber + ") " + mGenre + "]";
-        }
-    }
-
-    private void updateCurrentMediaState() {
-        // Only do player updates when we aren't registering for track changes.
-        MediaAttributes currentAttributes;
-        PlaybackState newState = updatePlaybackState();
-
-        synchronized (this) {
-            if (mMediaController == null) {
-                currentAttributes = new MediaAttributes(null);
-            } else {
-                currentAttributes = new MediaAttributes(mMediaController.getMetadata());
-            }
-        }
-
-        byte newPlayStatus = getBluetoothPlayState(newState);
-
-        if (newState.getState() != PlaybackState.STATE_BUFFERING
-                && newState.getState() != PlaybackState.STATE_NONE) {
-            long newQueueId = MediaSession.QueueItem.UNKNOWN_ID;
-            if (newState != null) {
-                newQueueId = newState.getActiveQueueItemId();
-            }
-            if (DEBUG) {
-                Log.v(TAG,
-                        "Media update: id " + mLastQueueId + "➡" + newQueueId + "? " + currentAttributes
-                                .toRedactedString() + " : " + mMediaAttributes.toRedactedString());
-            }
-
-            if (mAvailablePlayerViewChanged) {
-                registerNotificationRspAvalPlayerChangedNative(
-                        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);
-
-                mAvailablePlayerViewChanged = false;
-                mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-                mReportedPlayerID = mCurrAddrPlayerID;
-
-                // Update the now playing list without sending the notification
-                mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-                mAddressedMediaPlayer.updateNowPlayingList(mMediaController);
-                mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
-            }
-
-            // Dont send now playing list changed if the player doesn't support browsing
-            MediaPlayerInfo info = getAddressedPlayerInfo();
-            if (info != null && info.isBrowseSupported()) {
-                if (DEBUG) {
-                    Log.v(TAG, "Check if NowPlayingList is updated");
-                }
-                mAddressedMediaPlayer.updateNowPlayingList(mMediaController);
-            }
-
-            // Notify track changed if:
-            //  - The CT is registered for the notification
-            //  - Queue ID is UNKNOWN and MediaMetadata is different
-            //  - 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) {
-                Log.v(TAG, "Send track changed");
-                mMediaAttributes = currentAttributes;
-                mLastQueueId = newQueueId;
-                sendTrackChangedRsp(false);
-            }
-        } else {
-            Log.i(TAG, "Skipping update due to invalid playback state");
-        }
-
-        // still send the updated play state if the playback state is none or buffering
-        if (DEBUG) {
-            Log.v(TAG, "play status change " + mReportedPlayStatus + "➡" + newPlayStatus
-                    + " mPlayStatusChangedNT: " + mPlayStatusChangedNT);
-        }
-        if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM || (mReportedPlayStatus
-                != newPlayStatus)) {
-            sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_CHANGED, newPlayStatus);
-        }
-
-        sendPlayPosNotificationRsp(false);
-    }
-
-    private void getRcFeaturesRequestFromNative(byte[] address, int features) {
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) Log.d(TAG, "getRcFeaturesRequestFromNative: mHandler is already null");
-            return;
-        }
-
-        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_GET_RC_FEATURES, features, 0,
-                Utils.getAddressStringFromByte(address));
-        handler.sendMessage(msg);
-    }
-
-    private void getPlayStatusRequestFromNative(byte[] address) {
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) Log.d(TAG, "getPlayStatusRequestFromNative: mHandler is already null");
-            return;
-        }
-
-        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_GET_PLAY_STATUS);
-        msg.obj = address;
-        handler.sendMessage(msg);
-    }
-
-    private void getElementAttrRequestFromNative(byte[] address, byte numAttr, int[] attrs) {
-        AvrcpCmd avrcpCmdobj = new AvrcpCmd();
-        AvrcpCmd.ElementAttrCmd elemAttr = avrcpCmdobj.new ElementAttrCmd(address, numAttr, attrs);
-        Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_GET_ELEM_ATTRS);
-        msg.obj = elemAttr;
-        mHandler.sendMessage(msg);
-    }
-
-    private void registerNotificationRequestFromNative(byte[] address, int eventId, int param) {
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) {
-                Log.d(TAG, "registerNotificationRequestFromNative: mHandler is already null");
-            }
-            return;
-        }
-        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_REGISTER_NOTIFICATION, eventId, param);
-        msg.obj = address;
-        handler.sendMessage(msg);
-    }
-
-    private void processRegisterNotification(byte[] address, int eventId, int param) {
-        switch (eventId) {
-            case EVT_PLAY_STATUS_CHANGED:
-                mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-                updatePlaybackState();
-                sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_INTERIM, mReportedPlayStatus);
-                break;
-
-            case EVT_TRACK_CHANGED:
-                Log.v(TAG, "Track changed notification enabled");
-                mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
-                sendTrackChangedRsp(true);
-                break;
-
-            case EVT_PLAY_POS_CHANGED:
-                mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
-                mPlaybackIntervalMs = (long) param * 1000L;
-                sendPlayPosNotificationRsp(true);
-                break;
-
-            case EVT_AVBL_PLAYERS_CHANGED:
-                /* Notify remote available players changed */
-                if (DEBUG) {
-                    Log.d(TAG, "Available Players notification enabled");
-                }
-                registerNotificationRspAvalPlayerChangedNative(
-                        AvrcpConstants.NOTIFICATION_TYPE_INTERIM);
-                break;
-
-            case EVT_ADDR_PLAYER_CHANGED:
-                /* Notify remote addressed players changed */
-                if (DEBUG) {
-                    Log.d(TAG, "Addressed Player notification enabled");
-                }
-                registerNotificationRspAddrPlayerChangedNative(
-                        AvrcpConstants.NOTIFICATION_TYPE_INTERIM, mCurrAddrPlayerID, sUIDCounter);
-                mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
-                mReportedPlayerID = mCurrAddrPlayerID;
-                break;
-
-            case EVENT_UIDS_CHANGED:
-                if (DEBUG) {
-                    Log.d(TAG, "UIDs changed notification enabled");
-                }
-                registerNotificationRspUIDsChangedNative(AvrcpConstants.NOTIFICATION_TYPE_INTERIM,
-                        sUIDCounter);
-                break;
-
-            case EVENT_NOW_PLAYING_CONTENT_CHANGED:
-                if (DEBUG) {
-                    Log.d(TAG, "Now Playing List changed notification enabled");
-                }
-                /* send interim response to remote device */
-                mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
-                if (!registerNotificationRspNowPlayingChangedNative(
-                        AvrcpConstants.NOTIFICATION_TYPE_INTERIM)) {
-                    Log.e(TAG, "EVENT_NOW_PLAYING_CONTENT_CHANGED: "
-                            + "registerNotificationRspNowPlayingChangedNative for Interim rsp "
-                            + "failed!");
-                }
-                break;
-        }
-    }
-
-    private void handlePassthroughCmdRequestFromNative(byte[] address, int id, int keyState) {
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) {
-                Log.d(TAG, "handlePassthroughCmdRequestFromNative: mHandler is already null");
-            }
-            return;
-        }
-
-        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_PASS_THROUGH, id, keyState);
-        handler.sendMessage(msg);
-    }
-
-    private void sendTrackChangedRsp(boolean registering) {
-        if (!registering && mTrackChangedNT != AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
-            if (DEBUG) {
-                Log.d(TAG, "sendTrackChangedRsp: Not registered or registering.");
-            }
-            return;
-        }
-
-        mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-        if (registering) {
-            mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
-        }
-
-        MediaPlayerInfo info = getAddressedPlayerInfo();
-        // for non-browsable players or no player
-        if (info != null && !info.isBrowseSupported()) {
-            byte[] track = AvrcpConstants.TRACK_IS_SELECTED;
-            if (!mMediaAttributes.mExists) {
-                track = AvrcpConstants.NO_TRACK_SELECTED;
-            }
-            registerNotificationRspTrackChangeNative(mTrackChangedNT, track);
-            return;
-        }
-
-        mAddressedMediaPlayer.sendTrackChangeWithId(mTrackChangedNT, mMediaController);
-    }
-
-    private long getPlayPosition() {
-        if (mCurrentPlayState == null) {
-            return -1L;
-        }
-
-        if (mCurrentPlayState.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
-            return -1L;
-        }
-
-        if (isPlayingState(mCurrentPlayState)) {
-            long sinceUpdate =
-                    (SystemClock.elapsedRealtime() - mCurrentPlayState.getLastPositionUpdateTime());
-            return sinceUpdate + mCurrentPlayState.getPosition();
-        }
-
-        return mCurrentPlayState.getPosition();
-    }
-
-    private boolean isPlayingState(@Nullable PlaybackState state) {
-        if (state == null) {
-            return false;
-        }
-        return (state != null) && (state.getState() == PlaybackState.STATE_PLAYING);
-    }
-
-    /**
-     * Sends a play position notification, or schedules one to be
-     * sent later at an appropriate time. If |requested| is true,
-     * does both because this was called in reponse to a request from the
-     * TG.
-     */
-    private void sendPlayPosNotificationRsp(boolean requested) {
-        if (!requested && mPlayPosChangedNT != AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
-            if (DEBUG) {
-                Log.d(TAG, "sendPlayPosNotificationRsp: Not registered or requesting.");
-            }
-            return;
-        }
-
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) Log.d(TAG, "sendPlayPosNotificationRsp: handler is already null");
-            return;
-        }
-
-        long playPositionMs = getPlayPosition();
-        String debugLine = "sendPlayPosNotificationRsp: ";
-
-        // mNextPosMs is set to -1 when the previous position was invalid
-        // so this will be true if the new position is valid & old was invalid.
-        // mPlayPositionMs is set to -1 when the new position is invalid,
-        // and the old mPrevPosMs is >= 0 so this is true when the new is invalid
-        // and the old was valid.
-        if (DEBUG) {
-            debugLine += "(" + requested + ") " + mPrevPosMs + " <=? " + playPositionMs + " <=? "
-                    + mNextPosMs;
-            if (isPlayingState(mCurrentPlayState)) {
-                debugLine += " Playing";
-            }
-            debugLine += " State: " + mCurrentPlayState.getState();
-        }
-        if (requested || (
-                (mLastReportedPosition != playPositionMs) && (playPositionMs >= mNextPosMs) || (
-                        playPositionMs <= mPrevPosMs))) {
-            if (!requested) {
-                mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-            }
-            registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int) playPositionMs);
-            mLastReportedPosition = playPositionMs;
-            if (playPositionMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
-                mNextPosMs = playPositionMs + mPlaybackIntervalMs;
-                mPrevPosMs = playPositionMs - mPlaybackIntervalMs;
-            } else {
-                mNextPosMs = -1;
-                mPrevPosMs = -1;
-            }
-        }
-
-        handler.removeMessages(MSG_PLAY_INTERVAL_TIMEOUT);
-        if (mPlayPosChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM && isPlayingState(
-                mCurrentPlayState)) {
-            Message msg = handler.obtainMessage(MSG_PLAY_INTERVAL_TIMEOUT);
-            long delay = mPlaybackIntervalMs;
-            if (mNextPosMs != -1) {
-                delay = mNextPosMs - (playPositionMs > 0 ? playPositionMs : 0);
-            }
-            if (DEBUG) {
-                debugLine += " Timeout " + delay + "ms";
-            }
-            handler.sendMessageDelayed(msg, delay);
-        }
-        if (DEBUG) {
-            Log.d(TAG, debugLine);
-        }
-    }
-
-    /**
-     * This is called from AudioService. It will return whether this device supports abs volume.
-     * NOT USED AT THE MOMENT.
-     */
-    public boolean isAbsoluteVolumeSupported() {
-        return ((mFeatures & BTRC_FEAT_ABSOLUTE_VOLUME) != 0);
-    }
-
-    /**
-     * We get this call from AudioService. This will send a message to our handler object,
-     * requesting our handler to call setVolumeNative()
-     */
-    public void setAbsoluteVolume(int volume) {
-        if (volume == mLocalVolume) {
-            if (DEBUG) {
-                Log.v(TAG, "setAbsoluteVolume is setting same index, ignore " + volume);
-            }
-            return;
-        }
-
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) Log.d(TAG, "setAbsoluteVolume: mHandler is already null");
-            return;
-        }
-
-        Message msg = handler.obtainMessage(MSG_SET_ABSOLUTE_VOLUME, volume, 0);
-        handler.sendMessage(msg);
-    }
-
-    /* Called in the native layer as a btrc_callback to return the volume set on the carkit in the
-     * case when the volume is change locally on the carkit. This notification is not called when
-     * the volume is changed from the phone.
-     *
-     * This method will send a message to our handler to change the local stored volume and notify
-     * AudioService to update the UI
-     */
-    private void volumeChangeRequestFromNative(byte[] address, int volume, int ctype) {
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) Log.d(TAG, "volumeChangeRequestFromNative: mHandler is already null");
-            return;
-        }
-
-        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_VOLUME_CHANGE, volume, ctype);
-        Bundle data = new Bundle();
-        data.putByteArray("BdAddress", address);
-        msg.setData(data);
-        handler.sendMessage(msg);
-    }
-
-    private void getFolderItemsRequestFromNative(byte[] address, byte scope, long startItem,
-            long endItem, byte numAttr, int[] attrIds) {
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) Log.d(TAG, "getFolderItemsRequestFromNative: mHandler is already null");
-            return;
-        }
-        AvrcpCmd avrcpCmdobj = new AvrcpCmd();
-        AvrcpCmd.FolderItemsCmd folderObj =
-                avrcpCmdobj.new FolderItemsCmd(address, scope, startItem, endItem, numAttr,
-                        attrIds);
-        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_GET_FOLDER_ITEMS, 0, 0);
-        msg.obj = folderObj;
-        handler.sendMessage(msg);
-    }
-
-    private void setAddressedPlayerRequestFromNative(byte[] address, int playerId) {
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) Log.d(TAG, "setAddressedPlayerRequestFromNative: mHandler is already null");
-            return;
-        }
-
-        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_SET_ADDR_PLAYER, playerId, 0);
-        msg.obj = address;
-        handler.sendMessage(msg);
-    }
-
-    private void setBrowsedPlayerRequestFromNative(byte[] address, int playerId) {
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) Log.d(TAG, "setBrowsedPlayerRequestFromNative: mHandler is already null");
-            return;
-        }
-
-        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_SET_BR_PLAYER, playerId, 0);
-        msg.obj = address;
-        handler.sendMessage(msg);
-    }
-
-    private void changePathRequestFromNative(byte[] address, byte direction, byte[] folderUid) {
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) Log.d(TAG, "changePathRequestFromNative: mHandler is already null");
-            return;
-        }
-
-        Bundle data = new Bundle();
-        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_CHANGE_PATH);
-        data.putByteArray("BdAddress", address);
-        data.putByteArray("folderUid", folderUid);
-        data.putByte("direction", direction);
-        msg.setData(data);
-        handler.sendMessage(msg);
-    }
-
-    private void getItemAttrRequestFromNative(byte[] address, byte scope, byte[] itemUid,
-            int uidCounter, byte numAttr, int[] attrs) {
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) Log.d(TAG, "getItemAttrRequestFromNative: mHandler is already null");
-            return;
-        }
-        AvrcpCmd avrcpCmdobj = new AvrcpCmd();
-        AvrcpCmd.ItemAttrCmd itemAttr =
-                avrcpCmdobj.new ItemAttrCmd(address, scope, itemUid, uidCounter, numAttr, attrs);
-        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_GET_ITEM_ATTR);
-        msg.obj = itemAttr;
-        handler.sendMessage(msg);
-    }
-
-    private void searchRequestFromNative(byte[] address, int charsetId, byte[] searchStr) {
-        /* Search is not supported */
-        Log.w(TAG, "searchRequestFromNative: search is not supported");
-        searchRspNative(address, AvrcpConstants.RSP_SRCH_NOT_SPRTD, 0, 0);
-    }
-
-    private void playItemRequestFromNative(byte[] address, byte scope, int uidCounter, byte[] uid) {
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) Log.d(TAG, "playItemRequestFromNative: mHandler is already null");
-            return;
-        }
-
-        Bundle data = new Bundle();
-        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_PLAY_ITEM);
-        data.putByteArray("BdAddress", address);
-        data.putByteArray("uid", uid);
-        data.putInt("uidCounter", uidCounter);
-        data.putByte("scope", scope);
-
-        msg.setData(data);
-        handler.sendMessage(msg);
-    }
-
-    private void addToPlayListRequestFromNative(byte[] address, byte scope, byte[] uid,
-            int uidCounter) {
-        /* add to NowPlaying not supported */
-        Log.w(TAG, "addToPlayListRequestFromNative: not supported! scope=" + scope);
-        addToNowPlayingRspNative(address, AvrcpConstants.RSP_INTERNAL_ERR);
-    }
-
-    private void getTotalNumOfItemsRequestFromNative(byte[] address, byte scope) {
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) Log.d(TAG, "getTotalNumOfItemsRequestFromNative: mHandler is already null");
-            return;
-        }
-
-        Bundle data = new Bundle();
-        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_GET_TOTAL_NUM_OF_ITEMS);
-        msg.arg1 = scope;
-        msg.obj = address;
-        handler.sendMessage(msg);
-    }
-
-    private void notifyVolumeChanged(int volume) {
-        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
-                AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
-    }
-
-    private int convertToAudioStreamVolume(int volume) {
-        // Rescale volume to match AudioSystem's volume
-        return (int) Math.floor((double) volume * mAudioStreamMax / AVRCP_MAX_VOL);
-    }
-
-    private int convertToAvrcpVolume(int volume) {
-        return (int) Math.ceil((double) volume * AVRCP_MAX_VOL / mAudioStreamMax);
-    }
-
-    private void blackListCurrentDevice(String reason) {
-        mFeatures &= ~BTRC_FEAT_ABSOLUTE_VOLUME;
-        mAudioManager.avrcpSupportsAbsoluteVolume(mAddress, isAbsoluteVolumeSupported());
-
-        SharedPreferences pref =
-                mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, Context.MODE_PRIVATE);
-        SharedPreferences.Editor editor = pref.edit();
-
-        StringBuilder sb = new StringBuilder();
-        sb.append("Time: ");
-        sb.append(android.text.format.DateFormat.format("yyyy/MM/dd HH:mm:ss",
-                                                        System.currentTimeMillis()));
-        sb.append(" Reason: ");
-        sb.append(reason);
-        editor.putString(mAddress, sb.toString());
-        editor.apply();
-    }
-
-    private int modifyRcFeatureFromBlacklist(int feature, String address) {
-        SharedPreferences pref =
-                mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, Context.MODE_PRIVATE);
-        if (!pref.contains(address)) {
-            return feature;
-        }
-        return feature & ~BTRC_FEAT_ABSOLUTE_VOLUME;
-    }
-
-    public void resetBlackList(String address) {
-        SharedPreferences pref =
-                mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, Context.MODE_PRIVATE);
-        SharedPreferences.Editor editor = pref.edit();
-        editor.remove(address);
-        editor.apply();
-    }
-
-    /**
-     * This is called from A2dpStateMachine to set A2dp audio state.
-     */
-    public void setA2dpAudioState(int state) {
-        final AvrcpMessageHandler handler = mHandler;
-        if (handler == null) {
-            if (DEBUG) Log.d(TAG, "setA2dpAudioState: mHandler is already null");
-            return;
-        }
-
-        Message msg = handler.obtainMessage(MSG_SET_A2DP_AUDIO_STATE, state, 0);
-        handler.sendMessage(msg);
-    }
-
-    private class AvrcpServiceBootReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
-                if (DEBUG) {
-                    Log.d(TAG, "User unlocked, initializing player lists");
-                }
-                /* initializing media player's list */
-                buildBrowsablePlayerList();
-            }
-        }
-    }
-
-    private class AvrcpServiceBroadcastReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (DEBUG) {
-                Log.d(TAG, "AvrcpServiceBroadcastReceiver-> Action: " + action);
-            }
-
-            if (action.equals(Intent.ACTION_PACKAGE_REMOVED) || action.equals(
-                    Intent.ACTION_PACKAGE_DATA_CLEARED)) {
-                if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                    // a package is being removed, not replaced
-                    String packageName = intent.getData().getSchemeSpecificPart();
-                    if (packageName != null) {
-                        handlePackageModified(packageName, true);
-                    }
-                }
-
-            } else if (action.equals(Intent.ACTION_PACKAGE_ADDED) || action.equals(
-                    Intent.ACTION_PACKAGE_CHANGED)) {
-                String packageName = intent.getData().getSchemeSpecificPart();
-                if (DEBUG) {
-                    Log.d(TAG, "AvrcpServiceBroadcastReceiver-> packageName: " + packageName);
-                }
-                if (packageName != null) {
-                    handlePackageModified(packageName, false);
-                }
-            }
-        }
-    }
-
-    private void handlePackageModified(String packageName, boolean removed) {
-        if (DEBUG) {
-            Log.d(TAG, "packageName: " + packageName + " removed: " + removed);
-        }
-
-        if (removed) {
-            removeMediaPlayerInfo(packageName);
-            // old package is removed, updating local browsable player's list
-            if (isBrowseSupported(packageName)) {
-                removePackageFromBrowseList(packageName);
-            }
-        } else {
-            // new package has been added.
-            if (isBrowsableListUpdated(packageName)) {
-                // Rebuilding browsable players list
-                buildBrowsablePlayerList();
-            }
-        }
-    }
-
-    private boolean isBrowsableListUpdated(String newPackageName) {
-        // getting the browsable media players list from package manager
-        Intent intent = new Intent("android.media.browse.MediaBrowserService");
-        List<ResolveInfo> resInfos =
-                mPackageManager.queryIntentServices(intent, PackageManager.MATCH_ALL);
-        for (ResolveInfo resolveInfo : resInfos) {
-            if (resolveInfo.serviceInfo.packageName.equals(newPackageName)) {
-                if (DEBUG) {
-                    Log.d(TAG,
-                            "isBrowsableListUpdated: package includes MediaBrowserService, true");
-                }
-                return true;
-            }
-        }
-
-        // if list has different size
-        if (resInfos.size() != mBrowsePlayerInfoList.size()) {
-            if (DEBUG) {
-                Log.d(TAG, "isBrowsableListUpdated: browsable list size mismatch, true");
-            }
-            return true;
-        }
-
-        Log.d(TAG, "isBrowsableListUpdated: false");
-        return false;
-    }
-
-    private void removePackageFromBrowseList(String packageName) {
-        if (DEBUG) {
-            Log.d(TAG, "removePackageFromBrowseList: " + packageName);
-        }
-        synchronized (mBrowsePlayerInfoList) {
-            int browseInfoID = getBrowseId(packageName);
-            if (browseInfoID != -1) {
-                mBrowsePlayerInfoList.remove(browseInfoID);
-            }
-        }
-    }
-
-    /*
-     * utility function to get the browse player index from global browsable
-     * list. It may return -1 if specified package name is not in the list.
-     */
-    private int getBrowseId(String packageName) {
-        boolean response = false;
-        int browseInfoID = 0;
-        synchronized (mBrowsePlayerInfoList) {
-            for (BrowsePlayerInfo info : mBrowsePlayerInfoList) {
-                if (info.packageName.equals(packageName)) {
-                    response = true;
-                    break;
-                }
-                browseInfoID++;
-            }
-        }
-
-        if (!response) {
-            browseInfoID = -1;
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "getBrowseId for packageName: " + packageName + " , browseInfoID: "
-                    + browseInfoID);
-        }
-        return browseInfoID;
-    }
-
-    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;
-            }
-
-            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();
-            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);
-    }
-
-    private void setBrowsedPlayer(byte[] bdaddr, int selectedId) {
-        int status = AvrcpConstants.RSP_NO_ERROR;
-
-        // checking for error cases
-        if (mMediaPlayerInfoList.isEmpty()) {
-            status = AvrcpConstants.RSP_NO_AVBL_PLAY;
-            Log.w(TAG, "setBrowsedPlayer: No available players! ");
-        } else {
-            // Workaround for broken controllers selecting ID 0
-            // Seen at least on Ford, Chevrolet MyLink
-            if (selectedId == 0) {
-                Log.w(TAG, "setBrowsedPlayer: workaround invalid id 0");
-                selectedId = mCurrAddrPlayerID;
-            }
-
-            // update current browse player id and start browsing service
-            updateNewIds(mCurrAddrPlayerID, selectedId);
-            String browsedPackage = getPackageName(selectedId);
-
-            if (!isPackageNameValid(browsedPackage)) {
-                Log.w(TAG, " Invalid package for id:" + mCurrBrowsePlayerID);
-                status = AvrcpConstants.RSP_INV_PLAYER;
-            } else if (!isBrowseSupported(browsedPackage)) {
-                Log.w(TAG, "Browse unsupported for id:" + mCurrBrowsePlayerID + ", packagename : "
-                        + browsedPackage);
-                status = AvrcpConstants.RSP_PLAY_NOT_BROW;
-            } else if (!startBrowseService(bdaddr, browsedPackage)) {
-                Log.e(TAG, "service cannot be started for browse player id:" + mCurrBrowsePlayerID
-                        + ", packagename : " + browsedPackage);
-                status = AvrcpConstants.RSP_INTERNAL_ERR;
-            }
-        }
-
-        if (status != AvrcpConstants.RSP_NO_ERROR) {
-            setBrowsedPlayerRspNative(bdaddr, status, (byte) 0x00, 0, null);
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "setBrowsedPlayer for selectedId: " + selectedId + " , status: " + status);
-        }
-    }
-
-    private MediaSessionManager.OnActiveSessionsChangedListener mActiveSessionListener =
-            new MediaSessionManager.OnActiveSessionsChangedListener() {
-
-                @Override
-                public void onActiveSessionsChanged(
-                        List<android.media.session.MediaController> newControllers) {
-                    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));
-                        }
-                        // 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) {
-                        if (DEBUG) {
-                            Log.v(TAG, "No addressed player but active sessions, taking first.");
-                        }
-                        setAddressedMediaSessionPackage(newControllers.get(0).getPackageName());
-                    }
-                    updateCurrentMediaState();
-                }
-            };
-
-    private void setAddressedMediaSessionPackage(@Nullable String packageName) {
-        if (packageName == null) {
-            // Should only happen when there's no media players, reset to no available player.
-            updateCurrentController(0, mCurrBrowsePlayerID);
-            return;
-        }
-        if (packageName.equals("com.android.server.telecom")) {
-            Log.d(TAG, "Ignore addressed media session change to telecom");
-            return;
-        }
-        // No change.
-        if (getPackageName(mCurrAddrPlayerID).equals(packageName)) {
-            return;
-        }
-        if (DEBUG) {
-            Log.v(TAG, "Changing addressed media session to " + packageName);
-        }
-        // If the player doesn't exist, we need to add it.
-        if (getMediaPlayerInfo(packageName) == null) {
-            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());
-                    }
-                    updateCurrentController(newAddrID, mCurrBrowsePlayerID);
-                    updateCurrentMediaState();
-                    return;
-                }
-            }
-        }
-        // We shouldn't ever get here.
-        Log.e(TAG, "Player info for " + packageName + " doesn't exist!");
-    }
-
-    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")) {
-            Log.d(TAG, "Ignore active media session change to telecom");
-            return;
-        }
-        if (DEBUG) {
-            Log.v(TAG, "Set active media session " + activeController.getPackageName());
-        }
-        addMediaPlayerController(activeController);
-        setAddressedMediaSessionPackage(activeController.getPackageName());
-    }
-
-    private boolean startBrowseService(byte[] bdaddr, String packageName) {
-        boolean status = true;
-
-        /* creating new instance for Browse Media Player */
-        String browseService = getBrowseServiceName(packageName);
-        if (!browseService.isEmpty()) {
-            mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr)
-                    .setBrowsed(packageName, browseService);
-        } else {
-            Log.w(TAG, "No Browser service available for " + packageName);
-            status = false;
-        }
-
-        if (DEBUG) {
-            Log.d(TAG,
-                    "startBrowseService for packageName: " + packageName + ", status = " + status);
-        }
-        return status;
-    }
-
-    private String getBrowseServiceName(String packageName) {
-        String browseServiceName = "";
-
-        // getting the browse service name from browse player info
-        synchronized (mBrowsePlayerInfoList) {
-            int browseInfoID = getBrowseId(packageName);
-            if (browseInfoID != -1) {
-                browseServiceName = mBrowsePlayerInfoList.get(browseInfoID).serviceClass;
-            }
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "getBrowseServiceName for packageName: " + packageName
-                    + ", browseServiceName = " + browseServiceName);
-        }
-        return browseServiceName;
-    }
-
-    void buildBrowsablePlayerList() {
-        synchronized (mBrowsePlayerInfoList) {
-            mBrowsePlayerInfoList.clear();
-            Intent intent = new Intent(android.service.media.MediaBrowserService.SERVICE_INTERFACE);
-            List<ResolveInfo> playerList =
-                    mPackageManager.queryIntentServices(intent, PackageManager.MATCH_ALL);
-
-            for (ResolveInfo info : playerList) {
-                String displayableName = info.loadLabel(mPackageManager).toString();
-                String serviceName = info.serviceInfo.name;
-                String packageName = info.serviceInfo.packageName;
-
-                if (DEBUG) {
-                    Log.d(TAG, "Adding " + serviceName + " to list of browsable players");
-                }
-                BrowsePlayerInfo currentPlayer =
-                        new BrowsePlayerInfo(packageName, displayableName, serviceName);
-                mBrowsePlayerInfoList.add(currentPlayer);
-                MediaPlayerInfo playerInfo = getMediaPlayerInfo(packageName);
-                MediaController controller =
-                        (playerInfo == null) ? null : playerInfo.getMediaController();
-                // Refresh the media player entry so it notices we can browse
-                if (controller != null) {
-                    addMediaPlayerController(controller.getWrappedInstance());
-                } else {
-                    addMediaPlayerPackage(packageName);
-                }
-            }
-            updateCurrentMediaState();
-        }
-    }
-
-    /* Initializes list of media players identified from session manager active sessions */
-    private void initMediaPlayersList() {
-        synchronized (mMediaPlayerInfoList) {
-            // Clearing old browsable player's list
-            mMediaPlayerInfoList.clear();
-
-            if (mMediaSessionManager == null) {
-                if (DEBUG) {
-                    Log.w(TAG, "initMediaPlayersList: no media session manager!");
-                }
-                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);
-            }
-
-            updateCurrentMediaState();
-
-            if (mMediaPlayerInfoList.size() > 0) {
-                // Set the first one as the Addressed Player
-                updateCurrentController(mMediaPlayerInfoList.firstKey(), -1);
-            }
-        }
-    }
-
-    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());
-                }
-            }
-        }
-        return controllers;
-    }
-
-    /** Add (or update) a player to the media player list without a controller */
-    private boolean addMediaPlayerPackage(String packageName) {
-        MediaPlayerInfo info = new MediaPlayerInfo(null, AvrcpConstants.PLAYER_TYPE_AUDIO,
-                AvrcpConstants.PLAYER_SUBTYPE_NONE, PLAYSTATUS_STOPPED,
-                getFeatureBitMask(packageName), packageName, getAppLabel(packageName));
-        return addMediaPlayerInfo(info);
-    }
-
-    /** Add (or update) a player to the media player list given an active controller */
-    private boolean addMediaPlayerController(android.media.session.MediaController controller) {
-        String packageName = controller.getPackageName();
-        MediaPlayerInfo info = new MediaPlayerInfo(MediaControllerFactory.wrap(controller),
-                AvrcpConstants.PLAYER_TYPE_AUDIO, AvrcpConstants.PLAYER_SUBTYPE_NONE,
-                getBluetoothPlayState(controller.getPlaybackState()),
-                getFeatureBitMask(packageName), controller.getPackageName(),
-                getAppLabel(packageName));
-        return addMediaPlayerInfo(info);
-    }
-
-    /** Add or update a player to the media player list given the MediaPlayerInfo object.
-     *  @return true if an item was updated, false if it was added instead
-     */
-    private boolean addMediaPlayerInfo(MediaPlayerInfo info) {
-        int updateId = -1;
-        boolean updated = false;
-        boolean currentRemoved = false;
-        if (info.getPackageName().equals("com.android.server.telecom")) {
-            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);
-                        break;
-                    }
-                    updateId = id;
-                    updated = true;
-                    break;
-                }
-            }
-            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());
-        }
-        if (currentRemoved || updateId == mCurrAddrPlayerID) {
-            updateCurrentController(updateId, mCurrBrowsePlayerID);
-        }
-        return updated;
-    }
-
-    /** 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;
-                }
-            }
-            if (removeKey != -1) {
-                if (DEBUG) {
-                    Log.d(TAG, "remove #" + removeKey + ":" + mMediaPlayerInfoList.get(removeKey));
-                }
-                mAvailablePlayerViewChanged = true;
-                return mMediaPlayerInfoList.remove(removeKey);
-            }
-
-            return null;
-        }
-    }
-
-    /** Remove the controller referenced by |controller| from any player in the list */
-    private void removeMediaController(@Nullable android.media.session.MediaController controller) {
-        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);
-                    }
-                }
-            }
-        }
-    }
-
-    /*
-     * utility function to get the playback state of any media player through
-     * media controller APIs.
-     */
-    private byte getBluetoothPlayState(PlaybackState pbState) {
-        if (pbState == null) {
-            Log.w(TAG, "playState object null, sending STOPPED");
-            return PLAYSTATUS_STOPPED;
-        }
-
-        switch (pbState.getState()) {
-            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:
-                return PLAYSTATUS_PAUSED;
-
-            case PlaybackState.STATE_FAST_FORWARDING:
-            case PlaybackState.STATE_SKIPPING_TO_NEXT:
-            case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
-                return PLAYSTATUS_FWD_SEEK;
-
-            case PlaybackState.STATE_REWINDING:
-            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
-                return PLAYSTATUS_REV_SEEK;
-
-            case PlaybackState.STATE_ERROR:
-            default:
-                return PLAYSTATUS_ERROR;
-        }
-    }
-
-    /*
-     * utility function to get the feature bit mask of any media player through
-     * package name
-     */
-    private short[] getFeatureBitMask(String packageName) {
-
-        ArrayList<Short> featureBitsList = new ArrayList<Short>();
-
-        /* adding default feature bits */
-        featureBitsList.add(AvrcpConstants.AVRC_PF_PLAY_BIT_NO);
-        featureBitsList.add(AvrcpConstants.AVRC_PF_STOP_BIT_NO);
-        featureBitsList.add(AvrcpConstants.AVRC_PF_PAUSE_BIT_NO);
-        featureBitsList.add(AvrcpConstants.AVRC_PF_REWIND_BIT_NO);
-        featureBitsList.add(AvrcpConstants.AVRC_PF_FAST_FWD_BIT_NO);
-        featureBitsList.add(AvrcpConstants.AVRC_PF_FORWARD_BIT_NO);
-        featureBitsList.add(AvrcpConstants.AVRC_PF_BACKWARD_BIT_NO);
-        featureBitsList.add(AvrcpConstants.AVRC_PF_ADV_CTRL_BIT_NO);
-
-        /* Add/Modify browse player supported features. */
-        if (isBrowseSupported(packageName)) {
-            featureBitsList.add(AvrcpConstants.AVRC_PF_BROWSE_BIT_NO);
-            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);
-        }
-
-        // converting arraylist to array for response
-        short[] featureBitsArray = new short[featureBitsList.size()];
-
-        for (int i = 0; i < featureBitsList.size(); i++) {
-            featureBitsArray[i] = featureBitsList.get(i).shortValue();
-        }
-
-        return featureBitsArray;
-    }
-
-    /**
-     * Checks the Package name if it supports Browsing or not.
-     *
-     * @param packageName - name of the package to get the Id.
-     * @return true if it supports browsing, else false.
-     */
-    private boolean isBrowseSupported(String packageName) {
-        synchronized (mBrowsePlayerInfoList) {
-            /* check if Browsable Player's list contains this package name */
-            for (BrowsePlayerInfo info : mBrowsePlayerInfoList) {
-                if (info.packageName.equals(packageName)) {
-                    if (DEBUG) {
-                        Log.v(TAG, "isBrowseSupported for " + packageName + ": true");
-                    }
-                    return true;
-                }
-            }
-        }
-
-        if (DEBUG) {
-            Log.v(TAG, "isBrowseSupported for " + packageName + ": false");
-        }
-        return false;
-    }
-
-    private String getPackageName(int id) {
-        MediaPlayerInfo player = null;
-        synchronized (mMediaPlayerInfoList) {
-            player = mMediaPlayerInfoList.getOrDefault(id, null);
-        }
-
-        if (player == null) {
-            Log.w(TAG, "No package name for player (" + id + " not valid)");
-            return "";
-        }
-
-        String packageName = player.getPackageName();
-        if (DEBUG) {
-            Log.v(TAG, "Player " + id + " package: " + packageName);
-        }
-        return packageName;
-    }
-
-    /* from the global object, getting the current browsed player's package name */
-    private String getCurrentBrowsedPlayer(byte[] bdaddr) {
-        String browsedPlayerPackage = "";
-
-        Map<String, BrowsedMediaPlayer> connList = mAvrcpBrowseManager.getConnList();
-        String bdaddrStr = new String(bdaddr);
-        if (connList.containsKey(bdaddrStr)) {
-            browsedPlayerPackage = connList.get(bdaddrStr).getPackageName();
-        }
-        if (DEBUG) {
-            Log.v(TAG, "getCurrentBrowsedPlayerPackage: " + browsedPlayerPackage);
-        }
-        return browsedPlayerPackage;
-    }
-
-    /* Returns the MediaPlayerInfo for the currently addressed media player */
-    private MediaPlayerInfo getAddressedPlayerInfo() {
-        synchronized (mMediaPlayerInfoList) {
-            return mMediaPlayerInfoList.getOrDefault(mCurrAddrPlayerID, null);
-        }
-    }
-
-    /*
-     * Utility function to get the Media player info from package name returns
-     * null if package name not found in media players list
-     */
-    private MediaPlayerInfo getMediaPlayerInfo(String packageName) {
-        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.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;
-
-            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();
-
-                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++;
-                }
-            }
-
-            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;
-            }
-            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");
-        }
-        mediaPlayerListRspNative(folderObj.mAddress, rspObj.mStatus, rspObj.mUIDCounter,
-                rspObj.mItemType, rspObj.mNumItems, rspObj.mPlayerIds, rspObj.mPlayerTypes,
-                rspObj.mPlayerSubTypes, rspObj.mPlayStatusValues, rspObj.mFeatureBitMaskValues,
-                rspObj.mPlayerNameList);
-    }
-
-    /* unregister to the old controller, update new IDs and register to the new controller */
-    private boolean updateCurrentController(int addrId, int browseId) {
-        boolean registerRsp = true;
-
-        updateNewIds(addrId, browseId);
-
-        MediaController newController = null;
-        MediaPlayerInfo info = getAddressedPlayerInfo();
-        if (info != null) {
-            newController = info.getMediaController();
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "updateCurrentController: " + mMediaController + " to " + newController);
-        }
-        synchronized (this) {
-            if (mMediaController == null || (!mMediaController.equals(newController))) {
-                if (mMediaController != null) {
-                    mMediaController.unregisterCallback(mMediaControllerCb);
-                }
-                mMediaController = newController;
-                if (mMediaController != null) {
-                    mMediaController.registerCallback(mMediaControllerCb, mHandler);
-                } else {
-                    registerRsp = false;
-                }
-            }
-        }
-        updateCurrentMediaState();
-        return registerRsp;
-    }
-
-    /* Handle getfolderitems for scope = VFS, Search, NowPlayingList */
-    private void handleGetFolderItemBrowseResponse(AvrcpCmd.FolderItemsCmd folderObj,
-            byte[] bdaddr) {
-        int status = AvrcpConstants.RSP_NO_ERROR;
-
-        /* Browsed player is already set */
-        if (folderObj.mScope == AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
-            if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) == null) {
-                Log.e(TAG, "handleGetFolderItemBrowseResponse: no browsed player set for "
-                        + Utils.getAddressStringFromByte(bdaddr));
-                getFolderItemsRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR, (short) 0,
-                        (byte) 0x00, 0, null, null, null, null, null, null, null, null);
-                return;
-            }
-            mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).getFolderItemsVFS(folderObj);
-            return;
-        }
-        if (folderObj.mScope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
-            mAddressedMediaPlayer.getFolderItemsNowPlaying(bdaddr, folderObj, mMediaController);
-            return;
-        }
-
-        /* invalid scope */
-        Log.e(TAG, "handleGetFolderItemBrowseResponse: unknown scope " + folderObj.mScope);
-        getFolderItemsRspNative(bdaddr, AvrcpConstants.RSP_INV_SCOPE, (short) 0, (byte) 0x00, 0,
-                null, null, null, null, null, null, null, null);
-    }
-
-    /* utility function to update the global values of current Addressed and browsed player */
-    private void updateNewIds(int addrId, int browseId) {
-        if (DEBUG) {
-            Log.v(TAG,
-                    "updateNewIds: Addressed:" + mCurrAddrPlayerID + " to " + addrId + ", Browse:"
-                            + mCurrBrowsePlayerID + " to " + browseId);
-        }
-        mCurrAddrPlayerID = addrId;
-        mCurrBrowsePlayerID = browseId;
-    }
-
-    /* Getting the application's displayable name from package name */
-    private String getAppLabel(String packageName) {
-        ApplicationInfo appInfo = null;
-        try {
-            appInfo = mPackageManager.getApplicationInfo(packageName, 0);
-        } catch (NameNotFoundException e) {
-            e.printStackTrace();
-        }
-
-        return (String) (appInfo != null ? mPackageManager.getApplicationLabel(appInfo)
-                : "Unknown");
-    }
-
-    private void handlePlayItemResponse(byte[] bdaddr, byte[] uid, byte scope) {
-        if (scope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
-            mAddressedMediaPlayer.playItem(bdaddr, uid, mMediaController);
-        } else {
-            if (!isAddrPlayerSameAsBrowsed(bdaddr)) {
-                Log.w(TAG, "Remote requesting play item on uid which may not be recognized by"
-                        + "current addressed player");
-                playItemRspNative(bdaddr, AvrcpConstants.RSP_INV_ITEM);
-            }
-
-            if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) != null) {
-                mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).playItem(uid, scope);
-            } else {
-                Log.e(TAG, "handlePlayItemResponse: Remote requested playitem "
-                        + "before setbrowsedplayer");
-                playItemRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
-            }
-        }
-    }
-
-    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;
-        }
-        if (itemAttr.mScope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
-            if (mCurrAddrPlayerID == NO_PLAYER_ID) {
-                getItemAttrRspNative(itemAttr.mAddress, AvrcpConstants.RSP_NO_AVBL_PLAY, (byte) 0,
-                        null, null);
-                return;
-            }
-            mAddressedMediaPlayer.getItemAttr(itemAttr.mAddress, itemAttr, mMediaController);
-            return;
-        }
-        // All other scopes use browsed player
-        if (mAvrcpBrowseManager.getBrowsedMediaPlayer(itemAttr.mAddress) != null) {
-            mAvrcpBrowseManager.getBrowsedMediaPlayer(itemAttr.mAddress).getItemAttr(itemAttr);
-        } else {
-            Log.e(TAG, "Could not get attributes. mBrowsedMediaPlayer is null");
-            getItemAttrRspNative(itemAttr.mAddress, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0, null,
-                    null);
-        }
-    }
-
-    private void handleGetTotalNumOfItemsResponse(byte[] bdaddr, byte scope) {
-        // for scope as media player list
-        if (scope == AvrcpConstants.BTRC_SCOPE_PLAYER_LIST) {
-            int numPlayers = 0;
-            synchronized (mMediaPlayerInfoList) {
-                numPlayers = mMediaPlayerInfoList.size();
-            }
-            if (DEBUG) {
-                Log.d(TAG, "handleGetTotalNumOfItemsResponse: " + numPlayers + " players.");
-            }
-            getTotalNumOfItemsRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, numPlayers);
-        } else if (scope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
-            mAddressedMediaPlayer.getTotalNumOfItems(bdaddr, mMediaController);
-        } else {
-            // for FileSystem browsing scopes as VFS, Now Playing
-            if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) != null) {
-                mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).getTotalNumOfItems(scope);
-            } else {
-                Log.e(TAG, "Could not get Total NumOfItems. mBrowsedMediaPlayer is null");
-                getTotalNumOfItemsRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR, 0, 0);
-            }
-        }
-
-    }
-
-    /* check if browsed player and addressed player are same */
-    private boolean isAddrPlayerSameAsBrowsed(byte[] bdaddr) {
-        String browsedPlayer = getCurrentBrowsedPlayer(bdaddr);
-
-        if (!isPackageNameValid(browsedPlayer)) {
-            Log.w(TAG, "Browsed player name empty");
-            return false;
-        }
-
-        MediaPlayerInfo info = getAddressedPlayerInfo();
-        String packageName = (info == null) ? "<none>" : info.getPackageName();
-        if (info == null || !packageName.equals(browsedPlayer)) {
-            if (DEBUG) {
-                Log.d(TAG, browsedPlayer + " is not addressed player " + packageName);
-            }
-            return false;
-        }
-        return true;
-    }
-
-    /* checks if package name is not null or empty */
-    private boolean isPackageNameValid(String browsedPackage) {
-        boolean isValid = (browsedPackage != null && browsedPackage.length() > 0);
-        if (DEBUG) {
-            Log.d(TAG, "isPackageNameValid: browsedPackage = " + browsedPackage + "isValid = "
-                    + isValid);
-        }
-        return isValid;
-    }
-
-    /* checks if selected addressed player is already addressed */
-    private boolean isPlayerAlreadyAddressed(int selectedId) {
-        // checking if selected ID is same as the current addressed player id
-        boolean isAddressed = (mCurrAddrPlayerID == selectedId);
-        if (DEBUG) {
-            Log.d(TAG, "isPlayerAlreadyAddressed: isAddressed = " + isAddressed);
-        }
-        return isAddressed;
-    }
-
-    public void dump(StringBuilder sb) {
-        sb.append("AVRCP:\n");
-        ProfileService.println(sb, "mMediaAttributes: " + mMediaAttributes.toRedactedString());
-        ProfileService.println(sb, "mTransportControlFlags: " + mTransportControlFlags);
-        ProfileService.println(sb, "mCurrentPlayState: " + mCurrentPlayState);
-        ProfileService.println(sb, "mPlayStatusChangedNT: " + mPlayStatusChangedNT);
-        ProfileService.println(sb, "mTrackChangedNT: " + mTrackChangedNT);
-        ProfileService.println(sb, "mPlaybackIntervalMs: " + mPlaybackIntervalMs);
-        ProfileService.println(sb, "mPlayPosChangedNT: " + mPlayPosChangedNT);
-        ProfileService.println(sb, "mNextPosMs: " + mNextPosMs);
-        ProfileService.println(sb, "mPrevPosMs: " + mPrevPosMs);
-        ProfileService.println(sb, "mFeatures: " + mFeatures);
-        ProfileService.println(sb, "mRemoteVolume: " + mRemoteVolume);
-        ProfileService.println(sb, "mLastRemoteVolume: " + mLastRemoteVolume);
-        ProfileService.println(sb, "mLastDirection: " + mLastDirection);
-        ProfileService.println(sb, "mVolumeStep: " + mVolumeStep);
-        ProfileService.println(sb, "mAudioStreamMax: " + mAudioStreamMax);
-        ProfileService.println(sb, "mVolCmdSetInProgress: " + mVolCmdSetInProgress);
-        ProfileService.println(sb, "mAbsVolRetryTimes: " + mAbsVolRetryTimes);
-        ProfileService.println(sb, "mVolumeMapping: " + mVolumeMapping.toString());
-        synchronized (this) {
-            if (mMediaController != null) {
-                ProfileService.println(sb,
-                        "mMediaController: " + mMediaController.getWrappedInstance() + " pkg "
-                                + mMediaController.getPackageName());
-            }
-        }
-        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());
-            }
-        }
-
-        ProfileService.println(sb, "");
-        mAddressedMediaPlayer.dump(sb, mMediaController);
-
-        ProfileService.println(sb, "");
-        ProfileService.println(sb, mPassthroughDispatched + " passthrough operations: ");
-        if (mPassthroughDispatched > mPassthroughLogs.size()) {
-            ProfileService.println(sb, "  (last " + mPassthroughLogs.size() + ")");
-        }
-        synchronized (mPassthroughLogs) {
-            for (MediaKeyLog log : mPassthroughLogs) {
-                ProfileService.println(sb, "  " + log);
-            }
-        }
-        synchronized (mPassthroughPending) {
-            for (MediaKeyLog log : mPassthroughPending) {
-                ProfileService.println(sb, "  " + log);
-            }
-        }
-
-        // Print the blacklisted devices (for absolute volume control)
-        SharedPreferences pref =
-                mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, Context.MODE_PRIVATE);
-        Map<String, ?> allKeys = pref.getAll();
-        ProfileService.println(sb, "");
-        ProfileService.println(sb, "Runtime Blacklisted Devices (absolute volume):");
-        if (allKeys.isEmpty()) {
-            ProfileService.println(sb, "  None");
-        } else {
-            for (Map.Entry<String, ?> entry : allKeys.entrySet()) {
-                String key = entry.getKey();
-                Object value = entry.getValue();
-                if (value instanceof String) {
-                    ProfileService.println(sb, "  " + key + " " + value);
-                } else {
-                    ProfileService.println(sb, "  " + key + " Reason: Unknown");
-                }
-            }
-        }
-    }
-
-    public class AvrcpBrowseManager {
-        public Map<String, BrowsedMediaPlayer> connList = new HashMap<String, BrowsedMediaPlayer>();
-        private AvrcpMediaRspInterface mMediaInterface;
-        private Context mContext;
-
-        public AvrcpBrowseManager(Context context, AvrcpMediaRspInterface mediaInterface) {
-            mContext = context;
-            mMediaInterface = mediaInterface;
-        }
-
-        public void cleanup() {
-            Iterator entries = connList.entrySet().iterator();
-            while (entries.hasNext()) {
-                Map.Entry entry = (Map.Entry) entries.next();
-                BrowsedMediaPlayer browsedMediaPlayer = (BrowsedMediaPlayer) entry.getValue();
-                if (browsedMediaPlayer != null) {
-                    browsedMediaPlayer.cleanup();
-                }
-            }
-            // clean up the map
-            connList.clear();
-        }
-
-        // get the a free media player interface based on the passed bd address
-        // if the no items is found for the passed media player then it assignes a
-        // available media player interface
-        public BrowsedMediaPlayer getBrowsedMediaPlayer(byte[] bdaddr) {
-            BrowsedMediaPlayer mediaPlayer;
-            String bdaddrStr = new String(bdaddr);
-            if (connList.containsKey(bdaddrStr)) {
-                mediaPlayer = connList.get(bdaddrStr);
-            } else {
-                mediaPlayer = new BrowsedMediaPlayer(bdaddr, mContext, mMediaInterface);
-                connList.put(bdaddrStr, mediaPlayer);
-            }
-            return mediaPlayer;
-        }
-
-        // clears the details pertaining to passed bdaddres
-        public boolean clearBrowsedMediaPlayer(byte[] bdaddr) {
-            String bdaddrStr = new String(bdaddr);
-            if (connList.containsKey(bdaddrStr)) {
-                connList.remove(bdaddrStr);
-                return true;
-            }
-            return false;
-        }
-
-        public Map<String, BrowsedMediaPlayer> getConnList() {
-            return connList;
-        }
-
-        /* Helper function to convert colon separated bdaddr to byte string */
-        private byte[] hexStringToByteArray(String s) {
-            int len = s.length();
-            byte[] data = new byte[len / 2];
-            for (int i = 0; i < len; i += 2) {
-                data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(
-                        s.charAt(i + 1), 16));
-            }
-            return data;
-        }
-    }
-
-    /*
-     * private class which handles responses from AvrcpMediaManager. Maps responses to native
-     * responses. This class implements the AvrcpMediaRspInterface interface.
-     */
-    private class AvrcpMediaRsp implements AvrcpMediaRspInterface {
-        private static final String TAG = "AvrcpMediaRsp";
-
-        @Override
-        public void setAddrPlayerRsp(byte[] address, int rspStatus) {
-            if (!setAddressedPlayerRspNative(address, rspStatus)) {
-                Log.e(TAG, "setAddrPlayerRsp failed!");
-            }
-        }
-
-        @Override
-        public void setBrowsedPlayerRsp(byte[] address, int rspStatus, byte depth, int numItems,
-                String[] textArray) {
-            if (!setBrowsedPlayerRspNative(address, rspStatus, depth, numItems, textArray)) {
-                Log.e(TAG, "setBrowsedPlayerRsp failed!");
-            }
-        }
-
-        @Override
-        public void mediaPlayerListRsp(byte[] address, int rspStatus, MediaPlayerListRsp rspObj) {
-            if (rspObj != null && rspStatus == AvrcpConstants.RSP_NO_ERROR) {
-                if (!mediaPlayerListRspNative(address, rspStatus, sUIDCounter, rspObj.mItemType,
-                        rspObj.mNumItems, rspObj.mPlayerIds, rspObj.mPlayerTypes,
-                        rspObj.mPlayerSubTypes, rspObj.mPlayStatusValues,
-                        rspObj.mFeatureBitMaskValues, rspObj.mPlayerNameList)) {
-                    Log.e(TAG, "mediaPlayerListRsp failed!");
-                }
-            } else {
-                Log.e(TAG, "mediaPlayerListRsp: rspObj is null");
-                if (!mediaPlayerListRspNative(address, rspStatus, sUIDCounter, (byte) 0x00, 0, null,
-                        null, null, null, null, null)) {
-                    Log.e(TAG, "mediaPlayerListRsp failed!");
-                }
-            }
-        }
-
-        @Override
-        public void folderItemsRsp(byte[] address, int rspStatus, FolderItemsRsp rspObj) {
-            if (rspObj != null && rspStatus == AvrcpConstants.RSP_NO_ERROR) {
-                if (!getFolderItemsRspNative(address, rspStatus, sUIDCounter, rspObj.mScope,
-                        rspObj.mNumItems, rspObj.mFolderTypes, rspObj.mPlayable, rspObj.mItemTypes,
-                        rspObj.mItemUid, rspObj.mDisplayNames, rspObj.mAttributesNum,
-                        rspObj.mAttrIds, rspObj.mAttrValues)) {
-                    Log.e(TAG, "getFolderItemsRspNative failed!");
-                }
-            } else {
-                Log.e(TAG, "folderItemsRsp: rspObj is null or rspStatus is error:" + rspStatus);
-                if (!getFolderItemsRspNative(address, rspStatus, sUIDCounter, (byte) 0x00, 0, null,
-                        null, null, null, null, null, null, null)) {
-                    Log.e(TAG, "getFolderItemsRspNative failed!");
-                }
-            }
-
-        }
-
-        @Override
-        public void changePathRsp(byte[] address, int rspStatus, int numItems) {
-            if (!changePathRspNative(address, rspStatus, numItems)) {
-                Log.e(TAG, "changePathRspNative failed!");
-            }
-        }
-
-        @Override
-        public void getItemAttrRsp(byte[] address, int rspStatus, ItemAttrRsp rspObj) {
-            if (rspObj != null && rspStatus == AvrcpConstants.RSP_NO_ERROR) {
-                if (!getItemAttrRspNative(address, rspStatus, rspObj.mNumAttr,
-                        rspObj.mAttributesIds, rspObj.mAttributesArray)) {
-                    Log.e(TAG, "getItemAttrRspNative failed!");
-                }
-            } else {
-                Log.e(TAG, "getItemAttrRsp: rspObj is null or rspStatus is error:" + rspStatus);
-                if (!getItemAttrRspNative(address, rspStatus, (byte) 0x00, null, null)) {
-                    Log.e(TAG, "getItemAttrRspNative failed!");
-                }
-            }
-        }
-
-        @Override
-        public void playItemRsp(byte[] address, int rspStatus) {
-            if (!playItemRspNative(address, rspStatus)) {
-                Log.e(TAG, "playItemRspNative failed!");
-            }
-        }
-
-        @Override
-        public void getTotalNumOfItemsRsp(byte[] address, int rspStatus, int uidCounter,
-                int numItems) {
-            if (!getTotalNumOfItemsRspNative(address, rspStatus, sUIDCounter, numItems)) {
-                Log.e(TAG, "getTotalNumOfItemsRspNative failed!");
-            }
-        }
-
-        @Override
-        public void addrPlayerChangedRsp(int type, int playerId, int uidCounter) {
-            if (!registerNotificationRspAddrPlayerChangedNative(type, playerId, sUIDCounter)) {
-                Log.e(TAG, "registerNotificationRspAddrPlayerChangedNative failed!");
-            }
-        }
-
-        @Override
-        public void avalPlayerChangedRsp(byte[] address, int type) {
-            if (!registerNotificationRspAvalPlayerChangedNative(type)) {
-                Log.e(TAG, "registerNotificationRspAvalPlayerChangedNative failed!");
-            }
-        }
-
-        @Override
-        public void uidsChangedRsp(int type) {
-            if (!registerNotificationRspUIDsChangedNative(type, sUIDCounter)) {
-                Log.e(TAG, "registerNotificationRspUIDsChangedNative failed!");
-            }
-        }
-
-        @Override
-        public void nowPlayingChangedRsp(int type) {
-            if (mNowPlayingListChangedNT != AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
-                if (DEBUG) {
-                    Log.d(TAG, "NowPlayingListChanged: Not registered or requesting.");
-                }
-                return;
-            }
-
-            if (!registerNotificationRspNowPlayingChangedNative(type)) {
-                Log.e(TAG, "registerNotificationRspNowPlayingChangedNative failed!");
-            }
-            mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-        }
-
-        @Override
-        public void trackChangedRsp(int type, byte[] uid) {
-            if (!registerNotificationRspTrackChangeNative(type, uid)) {
-                Log.e(TAG, "registerNotificationRspTrackChangeNative failed!");
-            }
-        }
-    }
-
-    /* getters for some private variables */
-    public AvrcpBrowseManager getAvrcpBrowseManager() {
-        return mAvrcpBrowseManager;
-    }
-
-    /* PASSTHROUGH COMMAND MANAGEMENT */
-
-    void handlePassthroughCmd(int op, int state) {
-        int code = avrcpPassthroughToKeyCode(op);
-        if (code == KeyEvent.KEYCODE_UNKNOWN) {
-            Log.w(TAG, "Ignoring passthrough of unknown key " + op + " state " + state);
-            return;
-        }
-        int action = KeyEvent.ACTION_DOWN;
-        if (state == AvrcpConstants.KEY_STATE_RELEASE) {
-            action = KeyEvent.ACTION_UP;
-        }
-        KeyEvent event = new KeyEvent(action, code);
-        if (!KeyEvent.isMediaKey(code)) {
-            Log.w(TAG, "Passthrough non-media key " + op + " (code " + code + ") state " + state);
-        }
-
-        mMediaSessionManager.dispatchMediaKeyEvent(event);
-        addKeyPending(event);
-    }
-
-    private int avrcpPassthroughToKeyCode(int operation) {
-        switch (operation) {
-            case BluetoothAvrcp.PASSTHROUGH_ID_UP:
-                return KeyEvent.KEYCODE_DPAD_UP;
-            case BluetoothAvrcp.PASSTHROUGH_ID_DOWN:
-                return KeyEvent.KEYCODE_DPAD_DOWN;
-            case BluetoothAvrcp.PASSTHROUGH_ID_LEFT:
-                return KeyEvent.KEYCODE_DPAD_LEFT;
-            case BluetoothAvrcp.PASSTHROUGH_ID_RIGHT:
-                return KeyEvent.KEYCODE_DPAD_RIGHT;
-            case BluetoothAvrcp.PASSTHROUGH_ID_RIGHT_UP:
-                return KeyEvent.KEYCODE_DPAD_UP_RIGHT;
-            case BluetoothAvrcp.PASSTHROUGH_ID_RIGHT_DOWN:
-                return KeyEvent.KEYCODE_DPAD_DOWN_RIGHT;
-            case BluetoothAvrcp.PASSTHROUGH_ID_LEFT_UP:
-                return KeyEvent.KEYCODE_DPAD_UP_LEFT;
-            case BluetoothAvrcp.PASSTHROUGH_ID_LEFT_DOWN:
-                return KeyEvent.KEYCODE_DPAD_DOWN_LEFT;
-            case BluetoothAvrcp.PASSTHROUGH_ID_0:
-                return KeyEvent.KEYCODE_NUMPAD_0;
-            case BluetoothAvrcp.PASSTHROUGH_ID_1:
-                return KeyEvent.KEYCODE_NUMPAD_1;
-            case BluetoothAvrcp.PASSTHROUGH_ID_2:
-                return KeyEvent.KEYCODE_NUMPAD_2;
-            case BluetoothAvrcp.PASSTHROUGH_ID_3:
-                return KeyEvent.KEYCODE_NUMPAD_3;
-            case BluetoothAvrcp.PASSTHROUGH_ID_4:
-                return KeyEvent.KEYCODE_NUMPAD_4;
-            case BluetoothAvrcp.PASSTHROUGH_ID_5:
-                return KeyEvent.KEYCODE_NUMPAD_5;
-            case BluetoothAvrcp.PASSTHROUGH_ID_6:
-                return KeyEvent.KEYCODE_NUMPAD_6;
-            case BluetoothAvrcp.PASSTHROUGH_ID_7:
-                return KeyEvent.KEYCODE_NUMPAD_7;
-            case BluetoothAvrcp.PASSTHROUGH_ID_8:
-                return KeyEvent.KEYCODE_NUMPAD_8;
-            case BluetoothAvrcp.PASSTHROUGH_ID_9:
-                return KeyEvent.KEYCODE_NUMPAD_9;
-            case BluetoothAvrcp.PASSTHROUGH_ID_DOT:
-                return KeyEvent.KEYCODE_NUMPAD_DOT;
-            case BluetoothAvrcp.PASSTHROUGH_ID_ENTER:
-                return KeyEvent.KEYCODE_NUMPAD_ENTER;
-            case BluetoothAvrcp.PASSTHROUGH_ID_CLEAR:
-                return KeyEvent.KEYCODE_CLEAR;
-            case BluetoothAvrcp.PASSTHROUGH_ID_CHAN_UP:
-                return KeyEvent.KEYCODE_CHANNEL_UP;
-            case BluetoothAvrcp.PASSTHROUGH_ID_CHAN_DOWN:
-                return KeyEvent.KEYCODE_CHANNEL_DOWN;
-            case BluetoothAvrcp.PASSTHROUGH_ID_PREV_CHAN:
-                return KeyEvent.KEYCODE_LAST_CHANNEL;
-            case BluetoothAvrcp.PASSTHROUGH_ID_INPUT_SEL:
-                return KeyEvent.KEYCODE_TV_INPUT;
-            case BluetoothAvrcp.PASSTHROUGH_ID_DISP_INFO:
-                return KeyEvent.KEYCODE_INFO;
-            case BluetoothAvrcp.PASSTHROUGH_ID_HELP:
-                return KeyEvent.KEYCODE_HELP;
-            case BluetoothAvrcp.PASSTHROUGH_ID_PAGE_UP:
-                return KeyEvent.KEYCODE_PAGE_UP;
-            case BluetoothAvrcp.PASSTHROUGH_ID_PAGE_DOWN:
-                return KeyEvent.KEYCODE_PAGE_DOWN;
-            case BluetoothAvrcp.PASSTHROUGH_ID_POWER:
-                return KeyEvent.KEYCODE_POWER;
-            case BluetoothAvrcp.PASSTHROUGH_ID_VOL_UP:
-                return KeyEvent.KEYCODE_VOLUME_UP;
-            case BluetoothAvrcp.PASSTHROUGH_ID_VOL_DOWN:
-                return KeyEvent.KEYCODE_VOLUME_DOWN;
-            case BluetoothAvrcp.PASSTHROUGH_ID_MUTE:
-                return KeyEvent.KEYCODE_MUTE;
-            case BluetoothAvrcp.PASSTHROUGH_ID_PLAY:
-                return KeyEvent.KEYCODE_MEDIA_PLAY;
-            case BluetoothAvrcp.PASSTHROUGH_ID_STOP:
-                return KeyEvent.KEYCODE_MEDIA_STOP;
-            case BluetoothAvrcp.PASSTHROUGH_ID_PAUSE:
-                return KeyEvent.KEYCODE_MEDIA_PAUSE;
-            case BluetoothAvrcp.PASSTHROUGH_ID_RECORD:
-                return KeyEvent.KEYCODE_MEDIA_RECORD;
-            case BluetoothAvrcp.PASSTHROUGH_ID_REWIND:
-                return KeyEvent.KEYCODE_MEDIA_REWIND;
-            case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR:
-                return KeyEvent.KEYCODE_MEDIA_FAST_FORWARD;
-            case BluetoothAvrcp.PASSTHROUGH_ID_EJECT:
-                return KeyEvent.KEYCODE_MEDIA_EJECT;
-            case BluetoothAvrcp.PASSTHROUGH_ID_FORWARD:
-                return KeyEvent.KEYCODE_MEDIA_NEXT;
-            case BluetoothAvrcp.PASSTHROUGH_ID_BACKWARD:
-                return KeyEvent.KEYCODE_MEDIA_PREVIOUS;
-            case BluetoothAvrcp.PASSTHROUGH_ID_F1:
-                return KeyEvent.KEYCODE_F1;
-            case BluetoothAvrcp.PASSTHROUGH_ID_F2:
-                return KeyEvent.KEYCODE_F2;
-            case BluetoothAvrcp.PASSTHROUGH_ID_F3:
-                return KeyEvent.KEYCODE_F3;
-            case BluetoothAvrcp.PASSTHROUGH_ID_F4:
-                return KeyEvent.KEYCODE_F4;
-            case BluetoothAvrcp.PASSTHROUGH_ID_F5:
-                return KeyEvent.KEYCODE_F5;
-            // Fallthrough for all unknown key mappings
-            case BluetoothAvrcp.PASSTHROUGH_ID_SELECT:
-            case BluetoothAvrcp.PASSTHROUGH_ID_ROOT_MENU:
-            case BluetoothAvrcp.PASSTHROUGH_ID_SETUP_MENU:
-            case BluetoothAvrcp.PASSTHROUGH_ID_CONT_MENU:
-            case BluetoothAvrcp.PASSTHROUGH_ID_FAV_MENU:
-            case BluetoothAvrcp.PASSTHROUGH_ID_EXIT:
-            case BluetoothAvrcp.PASSTHROUGH_ID_SOUND_SEL:
-            case BluetoothAvrcp.PASSTHROUGH_ID_ANGLE:
-            case BluetoothAvrcp.PASSTHROUGH_ID_SUBPICT:
-            case BluetoothAvrcp.PASSTHROUGH_ID_VENDOR:
-            default:
-                return KeyEvent.KEYCODE_UNKNOWN;
-        }
-    }
-
-    private void addKeyPending(KeyEvent event) {
-        mPassthroughPending.add(new MediaKeyLog(System.currentTimeMillis(), event));
-    }
-
-    private void recordKeyDispatched(KeyEvent event, String packageName) {
-        long time = System.currentTimeMillis();
-        Log.v(TAG, "recordKeyDispatched: " + event + " dispatched to " + packageName);
-        setAddressedMediaSessionPackage(packageName);
-        synchronized (mPassthroughPending) {
-            Iterator<MediaKeyLog> pending = mPassthroughPending.iterator();
-            while (pending.hasNext()) {
-                MediaKeyLog log = pending.next();
-                if (log.addDispatch(time, event, packageName)) {
-                    mPassthroughDispatched++;
-                    mPassthroughLogs.add(log);
-                    pending.remove();
-                    return;
-                }
-            }
-            Log.w(TAG, "recordKeyDispatch: can't find matching log!");
-        }
-    }
-
-    private final MediaSessionManager.Callback mButtonDispatchCallback =
-            new MediaSessionManager.Callback() {
-                @Override
-                public void onMediaKeyEventDispatched(KeyEvent event, MediaSession.Token token) {
-                    // Get the package name
-                    android.media.session.MediaController controller =
-                            new android.media.session.MediaController(mContext, token);
-                    String targetPackage = controller.getPackageName();
-                    recordKeyDispatched(event, targetPackage);
-                }
-
-                @Override
-                public void onMediaKeyEventDispatched(KeyEvent event, ComponentName receiver) {
-                    recordKeyDispatched(event, receiver.getPackageName());
-                }
-
-                @Override
-                public void onAddressedPlayerChanged(MediaSession.Token token) {
-                    setActiveMediaSession(token);
-                }
-
-                @Override
-                public void onAddressedPlayerChanged(ComponentName receiver) {
-                    if (receiver == null) {
-                        // No active sessions, and no session to revive, give up.
-                        setAddressedMediaSessionPackage(null);
-                        return;
-                    }
-                    // We can still get a passthrough which will revive this player.
-                    setAddressedMediaSessionPackage(receiver.getPackageName());
-                }
-            };
-
-    // Do not modify without updating the HAL bt_rc.h files.
-
-    // match up with btrc_play_status_t enum of bt_rc.h
-    static final byte PLAYSTATUS_STOPPED = 0;
-    static final byte PLAYSTATUS_PLAYING = 1;
-    static final byte PLAYSTATUS_PAUSED = 2;
-    static final byte PLAYSTATUS_FWD_SEEK = 3;
-    static final byte PLAYSTATUS_REV_SEEK = 4;
-    static final byte PLAYSTATUS_ERROR = (byte) 255;
-
-    // match up with btrc_media_attr_t enum of bt_rc.h
-    static final int MEDIA_ATTR_TITLE = 1;
-    static final int MEDIA_ATTR_ARTIST = 2;
-    static final int MEDIA_ATTR_ALBUM = 3;
-    static final int MEDIA_ATTR_TRACK_NUM = 4;
-    static final int MEDIA_ATTR_NUM_TRACKS = 5;
-    static final int MEDIA_ATTR_GENRE = 6;
-    static final int MEDIA_ATTR_PLAYING_TIME = 7;
-
-    // match up with btrc_event_id_t enum of bt_rc.h
-    static final int EVT_PLAY_STATUS_CHANGED = 1;
-    static final int EVT_TRACK_CHANGED = 2;
-    static final int EVT_TRACK_REACHED_END = 3;
-    static final int EVT_TRACK_REACHED_START = 4;
-    static final int EVT_PLAY_POS_CHANGED = 5;
-    static final int EVT_BATT_STATUS_CHANGED = 6;
-    static final int EVT_SYSTEM_STATUS_CHANGED = 7;
-    static final int EVT_APP_SETTINGS_CHANGED = 8;
-    static final int EVENT_NOW_PLAYING_CONTENT_CHANGED = 9;
-    static final int EVT_AVBL_PLAYERS_CHANGED = 0xa;
-    static final int EVT_ADDR_PLAYER_CHANGED = 0xb;
-    static final int EVENT_UIDS_CHANGED = 0x0c;
-
-    private static native void classInitNative();
-
-    private native void initNative();
-
-    private native void cleanupNative();
-
-    private native boolean getPlayStatusRspNative(byte[] address, int playStatus, int songLen,
-            int songPos);
-
-    private native boolean getElementAttrRspNative(byte[] address, byte numAttr, int[] attrIds,
-            String[] textArray);
-
-    private native boolean registerNotificationRspPlayStatusNative(int type, int playStatus);
-
-    private native boolean registerNotificationRspTrackChangeNative(int type, byte[] track);
-
-    private native boolean registerNotificationRspPlayPosNative(int type, int playPos);
-
-    private native boolean setVolumeNative(int volume);
-
-    private native boolean sendPassThroughCommandNative(int keyCode, int keyState);
-
-    private native boolean setAddressedPlayerRspNative(byte[] address, int rspStatus);
-
-    private native boolean setBrowsedPlayerRspNative(byte[] address, int rspStatus, byte depth,
-            int numItems, String[] textArray);
-
-    private native boolean mediaPlayerListRspNative(byte[] address, int rsStatus, int uidCounter,
-            byte itemType, int numItems, int[] playerIds, byte[] playerTypes, int[] playerSubTypes,
-            byte[] playStatusValues, short[] featureBitMaskValues, String[] textArray);
-
-    private native boolean getFolderItemsRspNative(byte[] address, int rspStatus, short uidCounter,
-            byte scope, int numItems, byte[] folderTypes, byte[] playable, byte[] itemTypes,
-            byte[] itemUidArray, String[] textArray, int[] attributesNum, int[] attributesIds,
-            String[] attributesArray);
-
-    private native boolean changePathRspNative(byte[] address, int rspStatus, int numItems);
-
-    private native boolean getItemAttrRspNative(byte[] address, int rspStatus, byte numAttr,
-            int[] attrIds, String[] textArray);
-
-    private native boolean playItemRspNative(byte[] address, int rspStatus);
-
-    private native boolean getTotalNumOfItemsRspNative(byte[] address, int rspStatus,
-            int uidCounter, int numItems);
-
-    private native boolean searchRspNative(byte[] address, int rspStatus, int uidCounter,
-            int numItems);
-
-    private native boolean addToNowPlayingRspNative(byte[] address, int rspStatus);
-
-    private native boolean registerNotificationRspAddrPlayerChangedNative(int type, int playerId,
-            int uidCounter);
-
-    private native boolean registerNotificationRspAvalPlayerChangedNative(int type);
-
-    private native boolean registerNotificationRspUIDsChangedNative(int type, int uidCounter);
-
-    private native boolean registerNotificationRspNowPlayingChangedNative(int type);
-
-}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpConstants.java b/src/com/android/bluetooth/avrcp/AvrcpConstants.java
deleted file mode 100644
index 50a2eeb..0000000
--- a/src/com/android/bluetooth/avrcp/AvrcpConstants.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.avrcp;
-
-/*************************************************************************************************
- * 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.
- * Helps in easier modifications and future enhancements in the constants.
- ************************************************************************************************/
-
-/*
- * @hide
- */
-final class AvrcpConstants {
-
-    /* Do not modify without upating the HAL bt_rc.h file */
-    /** Response Error codes **/
-    static final byte RSP_BAD_CMD = 0x00; /* Invalid command */
-    static final byte RSP_BAD_PARAM = 0x01; /* Invalid parameter */
-    static final byte RSP_NOT_FOUND = 0x02; /* Specified parameter is
-                                                              * wrong or not found */
-    static final byte RSP_INTERNAL_ERR = 0x03; /* Internal Error */
-    static final byte RSP_NO_ERROR = 0x04; /* Operation Success */
-    static final byte RSP_UID_CHANGED = 0x05; /* UIDs changed */
-    static final byte RSP_RESERVED = 0x06; /* Reserved */
-    static final byte RSP_INV_DIRN = 0x07; /* Invalid direction */
-    static final byte RSP_INV_DIRECTORY = 0x08; /* Invalid directory */
-    static final byte RSP_INV_ITEM = 0x09; /* Invalid Item */
-    static final byte RSP_INV_SCOPE = 0x0a; /* Invalid scope */
-    static final byte RSP_INV_RANGE = 0x0b; /* Invalid range */
-    static final byte RSP_DIRECTORY = 0x0c; /* UID is a directory */
-    static final byte RSP_MEDIA_IN_USE = 0x0d; /* Media in use */
-    static final byte RSP_PLAY_LIST_FULL = 0x0e; /* Playing list full */
-    static final byte RSP_SRCH_NOT_SPRTD = 0x0f; /* Search not supported */
-    static final byte RSP_SRCH_IN_PROG = 0x10; /* Search in progress */
-    static final byte RSP_INV_PLAYER = 0x11; /* Invalid player */
-    static final byte RSP_PLAY_NOT_BROW = 0x12; /* Player not browsable */
-    static final byte RSP_PLAY_NOT_ADDR = 0x13; /* Player not addressed */
-    static final byte RSP_INV_RESULTS = 0x14; /* Invalid results */
-    static final byte RSP_NO_AVBL_PLAY = 0x15; /* No available players */
-    static final byte RSP_ADDR_PLAY_CHGD = 0x16; /* Addressed player changed */
-
-    /* valid scopes for get_folder_items */
-    static final byte BTRC_SCOPE_PLAYER_LIST = 0x00; /* Media Player List */
-    static final byte BTRC_SCOPE_FILE_SYSTEM = 0x01; /* Virtual File System */
-    static final byte BTRC_SCOPE_SEARCH = 0x02; /* Search */
-    static final byte BTRC_SCOPE_NOW_PLAYING = 0x03; /* Now Playing */
-
-    /* valid directions for change path */
-    static final byte DIR_UP = 0x00;
-    static final byte DIR_DOWN = 0x01;
-
-    /* item type to browse */
-    static final byte BTRC_ITEM_PLAYER = 0x01;
-    static final byte BTRC_ITEM_FOLDER = 0x02;
-    static final byte BTRC_ITEM_MEDIA = 0x03;
-
-    /* valid folder types */
-    static final byte FOLDER_TYPE_MIXED = 0x00;
-    static final byte FOLDER_TYPE_TITLES = 0x01;
-    static final byte FOLDER_TYPE_ALBUMS = 0x02;
-    static final byte FOLDER_TYPE_ARTISTS = 0x03;
-    static final byte FOLDER_TYPE_GENRES = 0x04;
-    static final byte FOLDER_TYPE_PLAYLISTS = 0x05;
-    static final byte FOLDER_TYPE_YEARS = 0x06;
-
-    /* valid playable flags */
-    static final byte ITEM_NOT_PLAYABLE = 0x00;
-    static final byte ITEM_PLAYABLE = 0x01;
-
-    /* valid Attribute ids for media elements */
-    static final int ATTRID_TITLE = 0x01;
-    static final int ATTRID_ARTIST = 0x02;
-    static final int ATTRID_ALBUM = 0x03;
-    static final int ATTRID_TRACK_NUM = 0x04;
-    static final int ATTRID_NUM_TRACKS = 0x05;
-    static final int ATTRID_GENRE = 0x06;
-    static final int ATTRID_PLAY_TIME = 0x07;
-    static final int ATTRID_COVER_ART = 0x08;
-
-    /* constants to send in Track change response */
-    static final byte[] NO_TRACK_SELECTED = {
-            (byte) 0xFF,
-            (byte) 0xFF,
-            (byte) 0xFF,
-            (byte) 0xFF,
-            (byte) 0xFF,
-            (byte) 0xFF,
-            (byte) 0xFF,
-            (byte) 0xFF
-    };
-    static final byte[] TRACK_IS_SELECTED = {
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00
-    };
-
-    /* UID size */
-    static final int UID_SIZE = 8;
-
-    static final short DEFAULT_UID_COUNTER = 0x0000;
-
-    /* Bitmask size for Media Players */
-    static final int AVRC_FEATURE_MASK_SIZE = 16;
-
-    /* Maximum attributes for media item */
-    static final int MAX_NUM_ATTR = 8;
-
-    /* notification types for remote device */
-    static final int NOTIFICATION_TYPE_INTERIM = 0;
-    static final int NOTIFICATION_TYPE_CHANGED = 1;
-
-    static final int TRACK_ID_SIZE = 8;
-
-    /* player feature bit mask constants */
-    static final short AVRC_PF_PLAY_BIT_NO = 40;
-    static final short AVRC_PF_STOP_BIT_NO = 41;
-    static final short AVRC_PF_PAUSE_BIT_NO = 42;
-    static final short AVRC_PF_REWIND_BIT_NO = 44;
-    static final short AVRC_PF_FAST_FWD_BIT_NO = 45;
-    static final short AVRC_PF_FORWARD_BIT_NO = 47;
-    static final short AVRC_PF_BACKWARD_BIT_NO = 48;
-    static final short AVRC_PF_ADV_CTRL_BIT_NO = 58;
-    static final short AVRC_PF_BROWSE_BIT_NO = 59;
-    static final short AVRC_PF_ADD2NOWPLAY_BIT_NO = 61;
-    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 byte PLAYER_TYPE_AUDIO = 1;
-    static final int PLAYER_SUBTYPE_NONE = 0;
-
-    // match up with btrc_play_status_t enum of bt_rc.h
-    static final int PLAYSTATUS_STOPPED = 0;
-    static final int PLAYSTATUS_PLAYING = 1;
-    static final int PLAYSTATUS_PAUSED = 2;
-    static final int PLAYSTATUS_FWD_SEEK = 3;
-    static final int PLAYSTATUS_REV_SEEK = 4;
-    static final int PLAYSTATUS_ERROR = 255;
-
-    static final byte NUM_ATTR_ALL = (byte) 0x00;
-    static final byte NUM_ATTR_NONE = (byte) 0xFF;
-
-    static final int KEY_STATE_PRESS = 1;
-    static final int KEY_STATE_RELEASE = 0;
-}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java b/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java
deleted file mode 100644
index ea6d7b8..0000000
--- a/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java
+++ /dev/null
@@ -1,451 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.avrcp;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.android.bluetooth.Utils;
-
-import java.util.ArrayDeque;
-import java.util.Arrays;
-import java.util.Collection;
-
-/*************************************************************************************************
- * Helper classes used for callback/response of browsing commands:-
- *     1) To bundle parameters for  native callbacks/response.
- *     2) Stores information of Addressed and Browsed Media Players.
- ************************************************************************************************/
-
-class AvrcpCmd {
-
-    AvrcpCmd() {}
-
-    /* Helper classes to pass parameters from callbacks to Avrcp handler */
-    class FolderItemsCmd {
-        byte mScope;
-        long mStartItem;
-        long mEndItem;
-        byte mNumAttr;
-        int[] mAttrIDs;
-        public byte[] mAddress;
-
-        FolderItemsCmd(byte[] address, byte scope, long startItem, long endItem, byte numAttr,
-                int[] attrIds) {
-            mAddress = address;
-            this.mScope = scope;
-            this.mStartItem = startItem;
-            this.mEndItem = endItem;
-            this.mNumAttr = numAttr;
-            this.mAttrIDs = attrIds;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder();
-            sb.append("[FolderItemCmd: scope " + mScope);
-            sb.append(" start " + mStartItem);
-            sb.append(" end " + mEndItem);
-            sb.append(" numAttr " + mNumAttr);
-            sb.append(" attrs: ");
-            for (int i = 0; i < mNumAttr; i++) {
-                sb.append(mAttrIDs[i] + " ");
-            }
-            return sb.toString();
-        }
-    }
-
-    class ItemAttrCmd {
-        byte mScope;
-        byte[] mUid;
-        int mUidCounter;
-        byte mNumAttr;
-        int[] mAttrIDs;
-        public byte[] mAddress;
-
-        ItemAttrCmd(byte[] address, byte scope, byte[] uid, int uidCounter, byte numAttr,
-                int[] attrIDs) {
-            mAddress = address;
-            mScope = scope;
-            mUid = uid;
-            mUidCounter = uidCounter;
-            mNumAttr = numAttr;
-            mAttrIDs = attrIDs;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder();
-            sb.append("[ItemAttrCmd: scope " + mScope);
-            sb.append(" uid " + Utils.byteArrayToString(mUid));
-            sb.append(" numAttr " + mNumAttr);
-            sb.append(" attrs: ");
-            for (int i = 0; i < mNumAttr; i++) {
-                sb.append(mAttrIDs[i] + " ");
-            }
-            return sb.toString();
-        }
-    }
-
-    class ElementAttrCmd {
-        byte mNumAttr;
-        int[] mAttrIDs;
-        public byte[] mAddress;
-
-        ElementAttrCmd(byte[] address, byte numAttr, int[] attrIDs) {
-            mAddress = address;
-            mNumAttr = numAttr;
-            mAttrIDs = attrIDs;
-        }
-    }
-}
-
-/* Helper classes to pass parameters to native response */
-class MediaPlayerListRsp {
-    byte mStatus;
-    short mUIDCounter;
-    byte mItemType;
-    int[] mPlayerIds;
-    byte[] mPlayerTypes;
-    int[] mPlayerSubTypes;
-    byte[] mPlayStatusValues;
-    short[] mFeatureBitMaskValues;
-    String[] mPlayerNameList;
-    int mNumItems;
-
-    MediaPlayerListRsp(byte status, short uidCounter, int numItems, byte itemType, int[] playerIds,
-            byte[] playerTypes, int[] playerSubTypes, byte[] playStatusValues,
-            short[] featureBitMaskValues, String[] playerNameList) {
-        this.mStatus = status;
-        this.mUIDCounter = uidCounter;
-        this.mNumItems = numItems;
-        this.mItemType = itemType;
-        this.mPlayerIds = playerIds;
-        this.mPlayerTypes = playerTypes;
-        this.mPlayerSubTypes = new int[numItems];
-        this.mPlayerSubTypes = playerSubTypes;
-        this.mPlayStatusValues = new byte[numItems];
-        this.mPlayStatusValues = playStatusValues;
-        int bitMaskSize = AvrcpConstants.AVRC_FEATURE_MASK_SIZE;
-        this.mFeatureBitMaskValues = new short[numItems * bitMaskSize];
-        for (int bitMaskIndex = 0; bitMaskIndex < (numItems * bitMaskSize); bitMaskIndex++) {
-            this.mFeatureBitMaskValues[bitMaskIndex] = featureBitMaskValues[bitMaskIndex];
-        }
-        this.mPlayerNameList = playerNameList;
-    }
-}
-
-class FolderItemsRsp {
-    byte mStatus;
-    short mUIDCounter;
-    byte mScope;
-    int mNumItems;
-    byte[] mFolderTypes;
-    byte[] mPlayable;
-    byte[] mItemTypes;
-    byte[] mItemUid;
-    String[] mDisplayNames; /* display name of the item. Eg: Folder name or song name */
-    int[] mAttributesNum;
-    int[] mAttrIds;
-    String[] mAttrValues;
-
-    FolderItemsRsp(byte status, short uidCounter, byte scope, int numItems, byte[] folderTypes,
-            byte[] playable, byte[] itemTypes, byte[] itemsUid, String[] displayNameArray,
-            int[] attributesNum, int[] attrIds, String[] attrValues) {
-        this.mStatus = status;
-        this.mUIDCounter = uidCounter;
-        this.mScope = scope;
-        this.mNumItems = numItems;
-        this.mFolderTypes = folderTypes;
-        this.mPlayable = playable;
-        this.mItemTypes = itemTypes;
-        this.mItemUid = itemsUid;
-        this.mDisplayNames = displayNameArray;
-        this.mAttributesNum = attributesNum;
-        this.mAttrIds = attrIds;
-        this.mAttrValues = attrValues;
-    }
-}
-
-class ItemAttrRsp {
-    byte mStatus;
-    byte mNumAttr;
-    int[] mAttributesIds;
-    String[] mAttributesArray;
-
-    ItemAttrRsp(byte status, int[] attributesIds, String[] attributesArray) {
-        mStatus = status;
-        mNumAttr = (byte) attributesIds.length;
-        mAttributesIds = attributesIds;
-        mAttributesArray = attributesArray;
-    }
-}
-
-/* stores information of Media Players in the system */
-class MediaPlayerInfo {
-
-    private byte mMajorType;
-    private int mSubType;
-    private byte mPlayStatus;
-    private short[] mFeatureBitMask;
-    @NonNull private String mPackageName;
-    @NonNull private String mDisplayableName;
-    @Nullable private MediaController mMediaController;
-
-    MediaPlayerInfo(@Nullable MediaController controller, byte majorType, int subType,
-            byte playStatus, short[] featureBitMask, @NonNull String packageName,
-            @Nullable String displayableName) {
-        this.setMajorType(majorType);
-        this.setSubType(subType);
-        this.mPlayStatus = playStatus;
-        // store a copy the FeatureBitMask array
-        this.mFeatureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length);
-        Arrays.sort(this.mFeatureBitMask);
-        this.setPackageName(packageName);
-        this.setDisplayableName(displayableName);
-        this.setMediaController(controller);
-    }
-
-    /* getters and setters */
-    byte getPlayStatus() {
-        return mPlayStatus;
-    }
-
-    void setPlayStatus(byte playStatus) {
-        this.mPlayStatus = playStatus;
-    }
-
-    MediaController getMediaController() {
-        return mMediaController;
-    }
-
-    void setMediaController(MediaController mediaController) {
-        if (mediaController != null) {
-            this.mPackageName = mediaController.getPackageName();
-        }
-        this.mMediaController = mediaController;
-    }
-
-    void setPackageName(@NonNull String name) {
-        // Controller determines package name when it is set.
-        if (mMediaController != null) {
-            return;
-        }
-        this.mPackageName = name;
-    }
-
-    String getPackageName() {
-        if (mMediaController != null) {
-            return mMediaController.getPackageName();
-        } else if (mPackageName != null) {
-            return mPackageName;
-        }
-        return null;
-    }
-
-    byte getMajorType() {
-        return mMajorType;
-    }
-
-    void setMajorType(byte majorType) {
-        this.mMajorType = majorType;
-    }
-
-    int getSubType() {
-        return mSubType;
-    }
-
-    void setSubType(int subType) {
-        this.mSubType = subType;
-    }
-
-    String getDisplayableName() {
-        return mDisplayableName;
-    }
-
-    void setDisplayableName(@Nullable String displayableName) {
-        if (displayableName == null) {
-            displayableName = "";
-        }
-        this.mDisplayableName = displayableName;
-    }
-
-    short[] getFeatureBitMask() {
-        return mFeatureBitMask;
-    }
-
-    void setFeatureBitMask(short[] featureBitMask) {
-        synchronized (this) {
-            this.mFeatureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length);
-            Arrays.sort(this.mFeatureBitMask);
-        }
-    }
-
-    boolean isBrowseSupported() {
-        synchronized (this) {
-            if (this.mFeatureBitMask == null) {
-                return false;
-            }
-            for (short bit : this.mFeatureBitMask) {
-                if (bit == AvrcpConstants.AVRC_PF_BROWSE_BIT_NO) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /** Tests if the view of this player presented to the controller is different enough to
-     *  justify sending an Available Players Changed update */
-    public boolean equalView(MediaPlayerInfo other) {
-        return (this.mMajorType == other.getMajorType()) && (this.mSubType == other.getSubType())
-                && Arrays.equals(this.mFeatureBitMask, other.getFeatureBitMask())
-                && this.mDisplayableName.equals(other.getDisplayableName());
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("MediaPlayerInfo ");
-        sb.append(getPackageName());
-        sb.append(" (as '" + getDisplayableName() + "')");
-        sb.append(" Type = " + getMajorType());
-        sb.append(", SubType = " + getSubType());
-        sb.append(", Status = " + mPlayStatus);
-        sb.append(" Feature Bits [");
-        short[] bits = getFeatureBitMask();
-        for (int i = 0; i < bits.length; i++) {
-            if (i != 0) {
-                sb.append(" ");
-            }
-            sb.append(bits[i]);
-        }
-        sb.append("] Controller: ");
-        sb.append(getMediaController());
-        return sb.toString();
-    }
-}
-
-/* stores information for browsable Media Players available in the system */
-class BrowsePlayerInfo {
-    public String packageName;
-    public String displayableName;
-    public String serviceClass;
-
-    BrowsePlayerInfo(String packageName, String displayableName, String serviceClass) {
-        this.packageName = packageName;
-        this.displayableName = displayableName;
-        this.serviceClass = serviceClass;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("BrowsePlayerInfo ");
-        sb.append(packageName);
-        sb.append(" ( as '" + displayableName + "')");
-        sb.append(" service " + serviceClass);
-        return sb.toString();
-    }
-}
-
-class FolderItemsData {
-    /* initialize sizes for rsp parameters */ int mNumItems;
-    int[] mAttributesNum;
-    byte[] mFolderTypes;
-    byte[] mItemTypes;
-    byte[] mPlayable;
-    byte[] mItemUid;
-    String[] mDisplayNames;
-    int[] mAttrIds;
-    String[] mAttrValues;
-    int mAttrCounter;
-
-    FolderItemsData(int size) {
-        mNumItems = size;
-        mAttributesNum = new int[size];
-
-        mFolderTypes = new byte[size]; /* folderTypes */
-        mItemTypes = new byte[size]; /* folder or media item */
-        mPlayable = new byte[size];
-        Arrays.fill(mFolderTypes, AvrcpConstants.FOLDER_TYPE_MIXED);
-        Arrays.fill(mItemTypes, AvrcpConstants.BTRC_ITEM_MEDIA);
-        Arrays.fill(mPlayable, AvrcpConstants.ITEM_PLAYABLE);
-
-        mItemUid = new byte[size * AvrcpConstants.UID_SIZE];
-        mDisplayNames = new String[size];
-
-        mAttrIds = null; /* array of attr ids */
-        mAttrValues = null; /* array of attr values */
-    }
-}
-
-/** A queue that evicts the first element when you add an element to the end when it reaches a
- * maximum size.
- * This is useful for keeping a FIFO queue of items where the items drop off the front, i.e. a log
- * with a maximum size.
- */
-class EvictingQueue<E> extends ArrayDeque<E> {
-    private int mMaxSize;
-
-    EvictingQueue(int maxSize) {
-        super();
-        mMaxSize = maxSize;
-    }
-
-    EvictingQueue(int maxSize, int initialElements) {
-        super(initialElements);
-        mMaxSize = maxSize;
-    }
-
-    EvictingQueue(int maxSize, Collection<? extends E> c) {
-        super(c);
-        mMaxSize = maxSize;
-    }
-
-    @Override
-    public void addFirst(E e) {
-        if (super.size() == mMaxSize) {
-            return;
-        }
-        super.addFirst(e);
-    }
-
-    @Override
-    public void addLast(E e) {
-        if (super.size() == mMaxSize) {
-            super.remove();
-        }
-        super.addLast(e);
-    }
-
-    @Override
-    public boolean offerFirst(E e) {
-        if (super.size() == mMaxSize) {
-            return false;
-        }
-        return super.offerFirst(e);
-    }
-
-    @Override
-    public boolean offerLast(E e) {
-        if (super.size() == mMaxSize) {
-            super.remove();
-        }
-        return super.offerLast(e);
-    }
-}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java b/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
deleted file mode 100644
index 8cab68e..0000000
--- a/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.avrcp;
-
-
-/*************************************************************************************************
- * Interface for classes which handle callbacks from AvrcpMediaManager.
- * These callbacks should map to native responses and used to communicate with the native layer.
- ************************************************************************************************/
-
-public interface AvrcpMediaRspInterface {
-    void setAddrPlayerRsp(byte[] address, int rspStatus);
-
-    void setBrowsedPlayerRsp(byte[] address, int rspStatus, byte depth, int numItems,
-            String[] textArray);
-
-    void mediaPlayerListRsp(byte[] address, int rspStatus, MediaPlayerListRsp rspObj);
-
-    void folderItemsRsp(byte[] address, int rspStatus, FolderItemsRsp rspObj);
-
-    void changePathRsp(byte[] address, int rspStatus, int numItems);
-
-    void getItemAttrRsp(byte[] address, int rspStatus, ItemAttrRsp rspObj);
-
-    void playItemRsp(byte[] address, int rspStatus);
-
-    void getTotalNumOfItemsRsp(byte[] address, int rspStatus, int uidCounter, int numItems);
-
-    void addrPlayerChangedRsp(int type, int playerId, int uidCounter);
-
-    void avalPlayerChangedRsp(byte[] address, int type);
-
-    void uidsChangedRsp(int type);
-
-    void nowPlayingChangedRsp(int type);
-
-    void trackChangedRsp(int type, byte[] uid);
-}
-
diff --git a/src/com/android/bluetooth/newavrcp/AvrcpNativeInterface.java b/src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java
similarity index 96%
rename from src/com/android/bluetooth/newavrcp/AvrcpNativeInterface.java
rename to src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java
index 4976237..7faa543 100644
--- a/src/com/android/bluetooth/newavrcp/AvrcpNativeInterface.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java
@@ -27,7 +27,7 @@
  * data.
  */
 public class AvrcpNativeInterface {
-    private static final String TAG = "NewAvrcpNativeInterface";
+    private static final String TAG = "AvrcpNativeInterface";
     private static final boolean DEBUG = true;
 
     private static AvrcpNativeInterface sInstance;
@@ -218,9 +218,9 @@
         mAvrcpService.deviceDisconnected(device);
     }
 
-    void sendVolumeChanged(int volume) {
+    void sendVolumeChanged(String bdaddr, int volume) {
         d("sendVolumeChanged: volume=" + volume);
-        sendVolumeChangedNative(volume);
+        sendVolumeChangedNative(bdaddr, volume);
     }
 
     void setVolume(int volume) {
@@ -245,7 +245,7 @@
     private native void cleanupNative();
     private native boolean connectDeviceNative(String bdaddr);
     private native boolean disconnectDeviceNative(String bdaddr);
-    private native void sendVolumeChangedNative(int volume);
+    private native void sendVolumeChangedNative(String bdaddr, int volume);
 
     private static void d(String msg) {
         if (DEBUG) {
diff --git a/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
similarity index 83%
rename from src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
rename to src/com/android/bluetooth/avrcp/AvrcpTargetService.java
index c7029ce..61ed75e 100644
--- a/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothAvrcpTarget;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -34,6 +35,7 @@
 import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.ServiceFactory;
 
 import java.util.List;
 import java.util.Objects;
@@ -43,7 +45,7 @@
  * @hide
  */
 public class AvrcpTargetService extends ProfileService {
-    private static final String TAG = "NewAvrcpTargetService";
+    private static final String TAG = "AvrcpTargetService";
     private static final boolean DEBUG = true;
     private static final String AVRCP_ENABLE_PROPERTY = "persist.bluetooth.enablenewavrcp";
 
@@ -55,6 +57,7 @@
     private AvrcpBroadcastReceiver mReceiver;
     private AvrcpNativeInterface mNativeInterface;
     private AvrcpVolumeManager mVolumeManager;
+    private ServiceFactory mFactory = new ServiceFactory();
 
     // Only used to see if the metadata has changed from its previous value
     private MediaData mCurrentData;
@@ -65,6 +68,8 @@
             MediaPlayerList.FolderUpdateCallback {
         @Override
         public void run(MediaData data) {
+            if (mNativeInterface == null) return;
+
             boolean metadata = !Objects.equals(mCurrentData.metadata, data.metadata);
             boolean state = !MediaPlayerWrapper.playstateEquals(mCurrentData.state, data.state);
             boolean queue = !Objects.equals(mCurrentData.queue, data.queue);
@@ -81,6 +86,8 @@
         @Override
         public void run(boolean availablePlayers, boolean addressedPlayers,
                 boolean uids) {
+            if (mNativeInterface == null) return;
+
             mNativeInterface.sendFolderUpdate(availablePlayers, addressedPlayers, uids);
         }
     }
@@ -94,6 +101,19 @@
 
                 // Update all the playback status info for each connected device
                 mNativeInterface.sendMediaUpdate(false, true, false);
+            } else if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
+                if (mNativeInterface == null) return;
+
+                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                if (device == null) return;
+
+                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+                if (state == BluetoothProfile.STATE_DISCONNECTED) {
+                    // If there is no connection, disconnectDevice() will do nothing
+                    if (mNativeInterface.disconnectDevice(device.getAddress())) {
+                        Log.d(TAG, "request to disconnect device " + device);
+                    }
+                }
             }
         }
     }
@@ -140,11 +160,6 @@
         Log.i(TAG, "Starting the AVRCP Target Service");
         mCurrentData = new MediaData(null, null, null);
 
-        mReceiver = new AvrcpBroadcastReceiver();
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
-        registerReceiver(mReceiver, filter);
-
         if (!SystemProperties.getBoolean(AVRCP_ENABLE_PROPERTY, true)) {
             Log.w(TAG, "Skipping initialization of the new AVRCP Target Service");
             sInstance = null;
@@ -156,15 +171,21 @@
 
         mMediaPlayerList = new MediaPlayerList(Looper.myLooper(), this);
 
+        mNativeInterface = AvrcpNativeInterface.getInterface();
+        mNativeInterface.init(AvrcpTargetService.this);
+
+        mVolumeManager = new AvrcpVolumeManager(this, mAudioManager, mNativeInterface);
+
         UserManager userManager = UserManager.get(getApplicationContext());
         if (userManager.isUserUnlocked()) {
             mMediaPlayerList.init(new ListCallback());
         }
 
-        mNativeInterface = AvrcpNativeInterface.getInterface();
-        mNativeInterface.init(AvrcpTargetService.this);
-
-        mVolumeManager = new AvrcpVolumeManager(this, mAudioManager, mNativeInterface);
+        mReceiver = new AvrcpBroadcastReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        registerReceiver(mReceiver, filter);
 
         // Only allow the service to be used once it is initialized
         sInstance = this;
@@ -227,30 +248,46 @@
     public void storeVolumeForDevice(BluetoothDevice device) {
         if (device == null) return;
 
+        List<BluetoothDevice> HAActiveDevices = null;
+        if (mFactory.getHearingAidService() != null) {
+            HAActiveDevices = mFactory.getHearingAidService().getActiveDevices();
+        }
+        if (HAActiveDevices != null
+                && (HAActiveDevices.get(0) != null || HAActiveDevices.get(1) != null)) {
+            Log.d(TAG, "Do not store volume when Hearing Aid devices is active");
+            return;
+        }
         mVolumeManager.storeVolumeForDevice(device);
     }
 
     /**
+     * Remove the stored volume for a device.
+     */
+    public void removeStoredVolumeForDevice(BluetoothDevice device) {
+        if (device == null) return;
+
+        mVolumeManager.removeStoredVolumeForDevice(device);
+    }
+
+    /**
      * Retrieve the remembered volume for a device. Returns -1 if there is no volume for the
      * device.
      */
     public int getRememberedVolumeForDevice(BluetoothDevice device) {
         if (device == null) return -1;
 
-        return mVolumeManager.getVolume(device, -1);
+        return mVolumeManager.getVolume(device, mVolumeManager.getNewDeviceVolume());
     }
 
     // TODO (apanicke): Add checks to blacklist Absolute Volume devices if they behave poorly.
     void setVolume(int avrcpVolume) {
-        int deviceVolume =
-                (int) Math.floor((double) avrcpVolume * sDeviceMaxVolume / AVRCP_MAX_VOL);
-        if (DEBUG) {
-            Log.d(TAG, "SendVolumeChanged: avrcpVolume=" + avrcpVolume
-                    + " deviceVolume=" + deviceVolume
-                    + " sDeviceMaxVolume=" + sDeviceMaxVolume);
+        BluetoothDevice activeDevice = mFactory.getA2dpService().getActiveDevice();
+        if (activeDevice == null) {
+            Log.d(TAG, "setVolume: no active device");
+            return;
         }
-        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, deviceVolume,
-                AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+
+        mVolumeManager.setVolume(activeDevice, avrcpVolume);
     }
 
     /**
@@ -258,15 +295,13 @@
      * volume.
      */
     public void sendVolumeChanged(int deviceVolume) {
-        int avrcpVolume =
-                (int) Math.floor((double) deviceVolume * AVRCP_MAX_VOL / sDeviceMaxVolume);
-        if (avrcpVolume > 127) avrcpVolume = 127;
-        if (DEBUG) {
-            Log.d(TAG, "SendVolumeChanged: avrcpVolume=" + avrcpVolume
-                    + " deviceVolume=" + deviceVolume
-                    + " sDeviceMaxVolume=" + sDeviceMaxVolume);
+        BluetoothDevice activeDevice = mFactory.getA2dpService().getActiveDevice();
+        if (activeDevice == null) {
+            Log.d(TAG, "sendVolumeChanged: no active device");
+            return;
         }
-        mNativeInterface.sendVolumeChanged(avrcpVolume);
+
+        mVolumeManager.sendVolumeChanged(activeDevice, deviceVolume);
     }
 
     Metadata getCurrentSongInfo() {
diff --git a/src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java b/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
similarity index 80%
rename from src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java
rename to src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
index 2e2ce42..c1369f8 100644
--- a/src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
@@ -32,7 +32,7 @@
 import java.util.Objects;
 
 class AvrcpVolumeManager extends AudioDeviceCallback {
-    public static final String TAG = "NewAvrcpVolumeManager";
+    public static final String TAG = "AvrcpVolumeManager";
     public static final boolean DEBUG = true;
 
     // All volumes are stored at system volume values, not AVRCP values
@@ -81,7 +81,7 @@
         if (mDeviceMap.get(device)) {
             int avrcpVolume = systemToAvrcpVolume(savedVolume);
             Log.i(TAG, "switchVolumeDevice: Updating device volume: avrcpVolume=" + avrcpVolume);
-            mNativeInterface.sendVolumeChanged(avrcpVolume);
+            mNativeInterface.sendVolumeChanged(device.getAddress(), avrcpVolume);
         }
     }
 
@@ -115,20 +115,35 @@
         volumeMapEditor.apply();
     }
 
-    void storeVolumeForDevice(BluetoothDevice device) {
+    synchronized void storeVolumeForDevice(@NonNull BluetoothDevice device) {
+        if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
+            return;
+        }
         SharedPreferences.Editor pref = getVolumeMap().edit();
         int storeVolume =  mAudioManager.getStreamVolume(STREAM_MUSIC);
         Log.i(TAG, "storeVolume: Storing stream volume level for device " + device
                 + " : " + storeVolume);
         mVolumeMap.put(device, storeVolume);
         pref.putInt(device.getAddress(), storeVolume);
-
         // Always use apply() since it is asynchronous, otherwise the call can hang waiting for
         // storage to be written.
         pref.apply();
     }
 
-    int getVolume(@NonNull BluetoothDevice device, int defaultValue) {
+    synchronized void removeStoredVolumeForDevice(@NonNull BluetoothDevice device) {
+        if (device.getBondState() != BluetoothDevice.BOND_NONE) {
+            return;
+        }
+        SharedPreferences.Editor pref = getVolumeMap().edit();
+        Log.i(TAG, "RemoveStoredVolume: Remove stored stream volume level for device " + device);
+        mVolumeMap.remove(device);
+        pref.remove(device.getAddress());
+        // Always use apply() since it is asynchronous, otherwise the call can hang waiting for
+        // storage to be written.
+        pref.apply();
+    }
+
+    synchronized int getVolume(@NonNull BluetoothDevice device, int defaultValue) {
         if (!mVolumeMap.containsKey(device)) {
             Log.w(TAG, "getVolume: Couldn't find volume preference for device: " + device);
             return defaultValue;
@@ -138,6 +153,36 @@
         return mVolumeMap.get(device);
     }
 
+    public int getNewDeviceVolume() {
+        return sNewDeviceVolume;
+    }
+
+    void setVolume(@NonNull BluetoothDevice device, int avrcpVolume) {
+        int deviceVolume =
+                (int) Math.floor((double) avrcpVolume * sDeviceMaxVolume / AVRCP_MAX_VOL);
+        if (DEBUG) {
+            Log.d(TAG, "setVolume: avrcpVolume=" + avrcpVolume
+                    + " deviceVolume=" + deviceVolume
+                    + " sDeviceMaxVolume=" + sDeviceMaxVolume);
+        }
+        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, deviceVolume,
+                AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+        storeVolumeForDevice(device);
+    }
+
+    void sendVolumeChanged(@NonNull BluetoothDevice device, int deviceVolume) {
+        int avrcpVolume =
+                (int) Math.floor((double) deviceVolume * AVRCP_MAX_VOL / sDeviceMaxVolume);
+        if (avrcpVolume > 127) avrcpVolume = 127;
+        if (DEBUG) {
+            Log.d(TAG, "sendVolumeChanged: avrcpVolume=" + avrcpVolume
+                    + " deviceVolume=" + deviceVolume
+                    + " sDeviceMaxVolume=" + sDeviceMaxVolume);
+        }
+        mNativeInterface.sendVolumeChanged(device.getAddress(), avrcpVolume);
+        storeVolumeForDevice(device);
+    }
+
     @Override
     public synchronized void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
         if (mCurrentDevice == null) {
diff --git a/src/com/android/bluetooth/newavrcp/BrowsablePlayerConnector.java b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
similarity index 90%
rename from src/com/android/bluetooth/newavrcp/BrowsablePlayerConnector.java
rename to src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
index ab23dbf..b5886c2 100644
--- a/src/com/android/bluetooth/newavrcp/BrowsablePlayerConnector.java
+++ b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
@@ -39,7 +39,7 @@
  * when constructing BrowsedPlayerWrappers by hand.
  */
 public class BrowsablePlayerConnector {
-    private static final String TAG = "NewAvrcpBrowsablePlayerConnector";
+    private static final String TAG = "AvrcpBrowsablePlayerConnector";
     private static final boolean DEBUG = true;
     private static final long CONNECT_TIMEOUT_MS = 10000; // Time in ms to wait for a connection
 
@@ -152,11 +152,7 @@
 
                     case MSG_TIMEOUT: {
                         Log.v(TAG, "Timed out waiting for players");
-                        for (BrowsedPlayerWrapper wrapper : mPendingPlayers) {
-                            if (DEBUG) Log.d(TAG, "Disconnecting " + wrapper.getPackageName());
-                            wrapper.disconnect();
-                        }
-                        mPendingPlayers.clear();
+                        removePendingPlayers();
                     } break;
                 }
 
@@ -169,4 +165,21 @@
             }
         };
     }
+
+    private void removePendingPlayers() {
+        for (BrowsedPlayerWrapper wrapper : mPendingPlayers) {
+            if (DEBUG) Log.d(TAG, "Disconnecting " + wrapper.getPackageName());
+            wrapper.disconnect();
+        }
+        mPendingPlayers.clear();
+    }
+
+    void cleanup() {
+        if (mPendingPlayers.size() != 0) {
+            Log.i(TAG, "Bluetooth turn off with " + mPendingPlayers.size() + " pending player(s)");
+            mHandler.removeMessages(MSG_TIMEOUT);
+            removePendingPlayers();
+            mHandler = null;
+        }
+    }
 }
diff --git a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
deleted file mode 100644
index 0889d90..0000000
--- a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
+++ /dev/null
@@ -1,842 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.avrcp;
-
-import android.annotation.NonNull;
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.media.browse.MediaBrowser;
-import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.MediaSession;
-import android.os.Bundle;
-import android.util.Log;
-
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Stack;
-
-/*************************************************************************************************
- * Provides functionality required for Browsed Media Player like browsing Virtual File System, get
- * Item Attributes, play item from the file system, etc.
- * Acts as an Interface to communicate with Media Browsing APIs for browsing FileSystem.
- ************************************************************************************************/
-
-class BrowsedMediaPlayer {
-    private static final boolean DEBUG = false;
-    private static final String TAG = "BrowsedMediaPlayer";
-
-    /* connection state with MediaBrowseService */
-    private static final int DISCONNECTED = 0;
-    private static final int CONNECTED = 1;
-    private static final int SUSPENDED = 2;
-
-    private static final String[] ROOT_FOLDER = {"root"};
-
-    /*  package and service name of target Media Player which is set for browsing */
-    private String mPackageName;
-    private String mConnectingPackageName;
-    private String mClassName;
-    private Context mContext;
-    private AvrcpMediaRspInterface mMediaInterface;
-    private byte[] mBDAddr;
-
-    /* Object used to connect to MediaBrowseService of Media Player */
-    private MediaBrowser mMediaBrowser = null;
-    private MediaController mMediaController = null;
-
-    /* The mediaId to be used for subscribing for children using the MediaBrowser */
-    private String mMediaId = null;
-    private String mRootFolderUid = null;
-    private int mConnState = DISCONNECTED;
-
-    /* stores the path trail during changePath */
-    private Stack<String> mPathStack = 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>();
-
-    /* command objects from avrcp handler */
-    private AvrcpCmd.FolderItemsCmd mFolderItemsReqObj;
-
-    /* store result of getfolderitems with scope="vfs" */
-    private List<MediaBrowser.MediaItem> mFolderItems = null;
-
-    /* Connection state callback handler */
-    class MediaConnectionCallback extends MediaBrowser.ConnectionCallback {
-        private String mCallbackPackageName;
-        private MediaBrowser mBrowser;
-
-        MediaConnectionCallback(String packageName) {
-            this.mCallbackPackageName = packageName;
-        }
-
-        public void setBrowser(MediaBrowser b) {
-            mBrowser = b;
-        }
-
-        @Override
-        public void onConnected() {
-            mConnState = CONNECTED;
-            if (DEBUG) {
-                Log.d(TAG, "mediaBrowser CONNECTED to " + mPackageName);
-            }
-            /* perform init tasks and set player as browsed player on successful connection */
-            onBrowseConnect(mCallbackPackageName, mBrowser);
-
-            // Remove what could be a circular dependency causing GC to never happen on this object
-            mBrowser = null;
-        }
-
-        @Override
-        public void onConnectionFailed() {
-            mConnState = DISCONNECTED;
-            // Remove what could be a circular dependency causing GC to never happen on this object
-            mBrowser = null;
-            Log.e(TAG, "mediaBrowser Connection failed with " + mPackageName
-                    + ", Sending fail response!");
-            mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
-                    (byte) 0x00, 0, null);
-        }
-
-        @Override
-        public void onConnectionSuspended() {
-            mBrowser = null;
-            mConnState = SUSPENDED;
-            Log.e(TAG, "mediaBrowser SUSPENDED connection with " + mPackageName);
-        }
-    }
-
-    /* Subscription callback handler. Subscribe to a folder to get its contents */
-    private MediaBrowser.SubscriptionCallback mFolderItemsCb =
-            new MediaBrowser.SubscriptionCallback() {
-
-                @Override
-                public void onChildrenLoaded(String parentId,
-                        List<MediaBrowser.MediaItem> children) {
-                    if (DEBUG) {
-                        Log.d(TAG, "OnChildren Loaded folder items: childrens= " + children.size());
-                    }
-
-            /*
-             * cache current folder items and send as rsp when remote requests
-             * get_folder_items (scope = vfs)
-             */
-                    if (mFolderItems == null) {
-                        if (DEBUG) {
-                            Log.d(TAG, "sending setbrowsed player rsp");
-                        }
-                        mFolderItems = children;
-                        mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR,
-                                (byte) 0x00, children.size(), ROOT_FOLDER);
-                    } else {
-                        mFolderItems = children;
-                        mCurrFolderNumItems = mFolderItems.size();
-                        mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR,
-                                mCurrFolderNumItems);
-                    }
-                    mMediaBrowser.unsubscribe(parentId);
-                }
-
-                /* UID is invalid */
-                @Override
-                public void onError(String id) {
-                    Log.e(TAG, "set browsed player rsp. Could not get root folder items");
-                    mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
-                            (byte) 0x00, 0, null);
-                }
-            };
-
-    /* callback from media player in response to getitemAttr request */
-    private class ItemAttribSubscriber extends MediaBrowser.SubscriptionCallback {
-        private String mMediaId;
-        private AvrcpCmd.ItemAttrCmd mAttrReq;
-
-        ItemAttribSubscriber(@NonNull AvrcpCmd.ItemAttrCmd attrReq, @NonNull String mediaId) {
-            mAttrReq = attrReq;
-            mMediaId = mediaId;
-        }
-
-        @Override
-        public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) {
-            String logprefix = "ItemAttribSubscriber(" + mMediaId + "): ";
-            if (DEBUG) {
-                Log.d(TAG, logprefix + "OnChildren Loaded");
-            }
-            int status = AvrcpConstants.RSP_INV_ITEM;
-
-            if (children == null) {
-                Log.w(TAG, logprefix + "children list is null parentId: " + parentId);
-            } else {
-                /* find the item in the folder */
-                for (MediaBrowser.MediaItem item : children) {
-                    if (item.getMediaId().equals(mMediaId)) {
-                        if (DEBUG) {
-                            Log.d(TAG, logprefix + "found item");
-                        }
-                        getItemAttrFilterAttr(item);
-                        status = AvrcpConstants.RSP_NO_ERROR;
-                        break;
-                    }
-                }
-            }
-            /* Send only error from here, in case of success, getItemAttrFilterAttr sends */
-            if (status != AvrcpConstants.RSP_NO_ERROR) {
-                Log.e(TAG, logprefix + "not able to find item from " + parentId);
-                mMediaInterface.getItemAttrRsp(mBDAddr, status, null);
-            }
-            mMediaBrowser.unsubscribe(parentId);
-        }
-
-        @Override
-        public void onError(String id) {
-            Log.e(TAG, "Could not get attributes from media player id: " + id);
-            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
-        }
-
-        /* helper method to filter required attibuteand send GetItemAttr response */
-        private void getItemAttrFilterAttr(@NonNull MediaBrowser.MediaItem mediaItem) {
-            /* Response parameters */
-            int[] attrIds = null; /* array of attr ids */
-            String[] attrValues = null; /* array of attr values */
-
-            /* variables to temperorily add attrs */
-            ArrayList<Integer> attrIdArray = new ArrayList<Integer>();
-            ArrayList<String> attrValueArray = new ArrayList<String>();
-            ArrayList<Integer> attrReqIds = new ArrayList<Integer>();
-
-            if (mAttrReq.mNumAttr == AvrcpConstants.NUM_ATTR_NONE) {
-                // Note(jamuraa): the stack should never send this, remove?
-                Log.i(TAG, "getItemAttrFilterAttr: No attributes requested");
-                mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_BAD_PARAM, null);
-                return;
-            }
-
-            /* check if remote device has requested all attributes */
-            if (mAttrReq.mNumAttr == AvrcpConstants.NUM_ATTR_ALL
-                    || mAttrReq.mNumAttr == AvrcpConstants.MAX_NUM_ATTR) {
-                for (int idx = 1; idx <= AvrcpConstants.MAX_NUM_ATTR; idx++) {
-                    attrReqIds.add(idx); /* attr id 0x00 is unused */
-                }
-            } else {
-                /* get only the requested attribute ids from the request */
-                for (int idx = 0; idx < mAttrReq.mNumAttr; idx++) {
-                    attrReqIds.add(mAttrReq.mAttrIDs[idx]);
-                }
-            }
-
-            /* lookup and copy values of attributes for ids requested above */
-            for (int attrId : attrReqIds) {
-                /* check if media player provided requested attributes */
-                String value = getAttrValue(attrId, mediaItem);
-                if (value != null) {
-                    attrIdArray.add(attrId);
-                    attrValueArray.add(value);
-                }
-            }
-
-            /* copy filtered attr ids and attr values to response parameters */
-            attrIds = new int[attrIdArray.size()];
-            for (int i = 0; i < attrIdArray.size(); i++) {
-                attrIds[i] = attrIdArray.get(i);
-            }
-
-            attrValues = attrValueArray.toArray(new String[attrIdArray.size()]);
-
-            /* create rsp object and send response */
-            ItemAttrRsp rspObj = new ItemAttrRsp(AvrcpConstants.RSP_NO_ERROR, attrIds, attrValues);
-            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
-        }
-    }
-
-    /* Constructor */
-    BrowsedMediaPlayer(byte[] address, Context context,
-            AvrcpMediaRspInterface mAvrcpMediaRspInterface) {
-        mContext = context;
-        mMediaInterface = mAvrcpMediaRspInterface;
-        mBDAddr = address;
-    }
-
-    /* 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);
-            return;
-        }
-        mConnectingPackageName = null;
-
-        if (browser == null) {
-            Log.e(TAG, "onBrowseConnect: received a null browser for " + connectedPackage);
-            mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
-                    (byte) 0x00, 0, null);
-            return;
-        }
-
-        MediaSession.Token token = null;
-        try {
-            if (!browser.isConnected()) {
-                Log.e(TAG, "setBrowsedPlayer: " + mPackageName + "not connected");
-            } else if ((token = browser.getSessionToken()) == null) {
-                Log.e(TAG, "setBrowsedPlayer: " + mPackageName + "no Session token");
-            } else {
-                /* update to the new MediaBrowser */
-                if (mMediaBrowser != null) {
-                    mMediaBrowser.disconnect();
-                }
-                mMediaBrowser = browser;
-                mPackageName = connectedPackage;
-
-                /* get rootfolder uid from media player */
-                if (mMediaId == null) {
-                    mMediaId = mMediaBrowser.getRoot();
-                    /*
-                     * assuming that root folder uid will not change on uids changed
-                     */
-                    mRootFolderUid = mMediaId;
-                    /* store root folder uid to stack */
-                    mPathStack.push(mMediaId);
-                }
-
-                mMediaController = MediaControllerFactory.make(mContext, token);
-                /* get root folder items */
-                mMediaBrowser.subscribe(mRootFolderUid, mFolderItemsCb);
-                return;
-            }
-        } catch (NullPointerException ex) {
-            Log.e(TAG, "setBrowsedPlayer : Null pointer during init");
-            ex.printStackTrace();
-        }
-
-        mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0x00,
-                0, null);
-    }
-
-    public void setBrowsed(String packageName, String cls) {
-        mConnectingPackageName = 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
-         */
-        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);
-
-        tempBrowser.connect();
-    }
-
-    /* called when connection to media player is closed */
-    public void cleanup() {
-        if (DEBUG) {
-            Log.d(TAG, "cleanup");
-        }
-
-        if (mConnState != DISCONNECTED) {
-            mMediaBrowser.disconnect();
-        }
-
-        mHmap = null;
-        mMediaController = null;
-        mMediaBrowser = null;
-        mPathStack = null;
-    }
-
-    public boolean isPlayerConnected() {
-        if (mMediaBrowser == null) {
-            if (DEBUG) {
-                Log.d(TAG, "isPlayerConnected: mMediaBrowser = null!");
-            }
-            return false;
-        }
-
-        return mMediaBrowser.isConnected();
-    }
-
-    /* returns number of items in new path as reponse */
-    public void changePath(byte[] folderUid, byte direction) {
-        if (DEBUG) {
-            Log.d(TAG, "changePath.direction = " + direction);
-        }
-        String newPath = "";
-
-        if (!isPlayerConnected()) {
-            Log.w(TAG, "changePath: disconnected from player service, sending internal error");
-            mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0);
-            return;
-        }
-
-        if (mMediaBrowser == null) {
-            Log.e(TAG, "Media browser is null, sending internal error");
-            mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0);
-            return;
-        }
-
-        /* check direction and change the path */
-        if (direction == AvrcpConstants.DIR_DOWN) { /* move down */
-            if ((newPath = byteToString(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)) {
-                /* new path is not browsable */
-                Log.e(TAG, "ItemUid received from changePath cmd is not browsable");
-                mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRECTORY, 0);
-            } else if (mPathStack.peek().equals(newPath)) {
-                /* new_folder is same as current folder */
-                Log.e(TAG, "new_folder is same as current folder, Invalid direction!");
-                mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
-            } else {
-                mMediaBrowser.subscribe(newPath, mFolderItemsCb);
-                /* assume that call is success and update stack with new folder path */
-                mPathStack.push(newPath);
-            }
-        } else if (direction == AvrcpConstants.DIR_UP) { /* move up */
-            if (!isBrowsableFolderUp()) {
-                /* Already on the root, cannot allow up: PTS: test case TC_TG_MCN_CB_BI_02_C
-                 * This is required, otherwise some CT will keep on sending change path up
-                 * until they receive error */
-                Log.w(TAG, "Cannot go up from now, already in the root, Invalid direction!");
-                mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
-            } else {
-                /* move folder up */
-                mPathStack.pop();
-                newPath = mPathStack.peek();
-                mMediaBrowser.subscribe(newPath, mFolderItemsCb);
-            }
-        } else { /* invalid direction */
-            Log.w(TAG, "changePath : Invalid direction " + direction);
-            mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
-        }
-    }
-
-    public void getItemAttr(AvrcpCmd.ItemAttrCmd itemAttr) {
-        String mediaID;
-        if (DEBUG) {
-            Log.d(TAG, "getItemAttr");
-        }
-
-        /* check if uid is valid by doing a lookup in hashmap */
-        mediaID = byteToString(itemAttr.mUid);
-        if (mediaID == null) {
-            Log.e(TAG, "uid is invalid");
-            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM, null);
-            return;
-        }
-
-        /* check scope */
-        if (itemAttr.mScope != AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
-            Log.e(TAG, "invalid scope");
-            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, null);
-            return;
-        }
-
-        if (mMediaBrowser == null) {
-            Log.e(TAG, "mMediaBrowser is null");
-            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
-            return;
-        }
-
-        /* Subscribe to the parent to list items and retrieve the right one */
-        mMediaBrowser.subscribe(mPathStack.peek(), new ItemAttribSubscriber(itemAttr, mediaID));
-    }
-
-    public void getTotalNumOfItems(byte scope) {
-        if (DEBUG) {
-            Log.d(TAG, "getTotalNumOfItems scope = " + scope);
-        }
-        if (scope != AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
-            Log.e(TAG, "getTotalNumOfItems error" + scope);
-            mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, 0, 0);
-            return;
-        }
-
-        if (mFolderItems == null) {
-            Log.e(TAG, "mFolderItems is null, sending internal error");
-            /* folderitems were not fetched during change path */
-            mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0, 0);
-            return;
-        }
-
-        /* find num items using size of already cached folder items */
-        mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, 0,
-                mFolderItems.size());
-    }
-
-    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 (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");
-            mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
-            return;
-        }
-
-        /* Filter attributes based on the request and send response to remote device */
-        getFolderItemsFilterAttr(mBDAddr, reqObj, mFolderItems,
-                AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM, mFolderItemsReqObj.mStartItem,
-                mFolderItemsReqObj.mEndItem);
-    }
-
-    /* Instructs media player to play particular media item */
-    public void playItem(byte[] uid, byte scope) {
-        String folderUid;
-
-        if (isPlayerConnected()) {
-            /* check if uid is valid */
-            if ((folderUid = byteToString(uid)) == null) {
-                Log.e(TAG, "uid is invalid!");
-                mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM);
-                return;
-            }
-
-            if (mMediaController != null) {
-                MediaController.TransportControls mediaControllerCntrl =
-                        mMediaController.getTransportControls();
-                if (DEBUG) {
-                    Log.d(TAG, "Sending playID: " + folderUid);
-                }
-
-                if (scope == AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
-                    mediaControllerCntrl.playFromMediaId(folderUid, null);
-                    mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR);
-                } else {
-                    Log.e(TAG, "playItem received for invalid scope!");
-                    mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE);
-                }
-            } else {
-                Log.e(TAG, "mediaController is null");
-                mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR);
-            }
-        } else {
-            Log.e(TAG, "playItem: Not connected to media player");
-            mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR);
-        }
-    }
-
-    /*
-     * helper method to check if startItem and endItem index is with range of
-     * MediaItem list. (Resultset containing all items in current path)
-     */
-    private List<MediaBrowser.MediaItem> checkIndexOutofBounds(byte[] bdaddr,
-            List<MediaBrowser.MediaItem> children, long startItem, long endItem) {
-        if (endItem >= children.size()) {
-            endItem = children.size() - 1;
-        }
-        if (startItem >= Integer.MAX_VALUE) {
-            startItem = Integer.MAX_VALUE;
-        }
-        try {
-            List<MediaBrowser.MediaItem> childrenSubList =
-                    children.subList((int) startItem, (int) endItem + 1);
-            if (childrenSubList.isEmpty()) {
-                Log.i(TAG, "childrenSubList is empty.");
-                throw new IndexOutOfBoundsException();
-            }
-            return childrenSubList;
-        } catch (IndexOutOfBoundsException ex) {
-            Log.w(TAG, "Index out of bounds start item =" + startItem + " end item = " + Math.min(
-                    children.size(), endItem + 1));
-            return null;
-        } catch (IllegalArgumentException ex) {
-            Log.i(TAG, "Index out of bounds start item =" + startItem + " > size");
-            return null;
-        }
-    }
-
-
-    /*
-     * helper method to filter required attibutes before sending GetFolderItems response
-     */
-    public void getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd mFolderItemsReqObj,
-            List<MediaBrowser.MediaItem> children, byte scope, long startItem, long endItem) {
-        if (DEBUG) {
-            Log.d(TAG,
-                    "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = " + endItem);
-        }
-
-        List<MediaBrowser.MediaItem> resultItems = new ArrayList<MediaBrowser.MediaItem>();
-
-        if (children == null) {
-            Log.e(TAG, "Error: children are null in getFolderItemsFilterAttr");
-            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
-            return;
-        }
-
-        /* check for index out of bound errors */
-        resultItems = checkIndexOutofBounds(bdaddr, children, startItem, endItem);
-        if (resultItems == null) {
-            Log.w(TAG, "resultItems is null.");
-            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
-            return;
-        }
-        FolderItemsData folderDataNative = new FolderItemsData(resultItems.size());
-
-        /* variables to temperorily add attrs */
-        ArrayList<String> attrArray = new ArrayList<String>();
-        ArrayList<Integer> attrId = new ArrayList<Integer>();
-
-        for (int itemIndex = 0; itemIndex < resultItems.size(); itemIndex++) {
-            /* item type. Needs to be set by media player */
-            MediaBrowser.MediaItem item = resultItems.get(itemIndex);
-            int flags = item.getFlags();
-            if ((flags & MediaBrowser.MediaItem.FLAG_BROWSABLE) != 0) {
-                folderDataNative.mItemTypes[itemIndex] = AvrcpConstants.BTRC_ITEM_FOLDER;
-            } else {
-                folderDataNative.mItemTypes[itemIndex] = AvrcpConstants.BTRC_ITEM_MEDIA;
-            }
-
-            /* set playable */
-            if ((flags & MediaBrowser.MediaItem.FLAG_PLAYABLE) != 0) {
-                folderDataNative.mPlayable[itemIndex] = AvrcpConstants.ITEM_PLAYABLE;
-            } else {
-                folderDataNative.mPlayable[itemIndex] = AvrcpConstants.ITEM_NOT_PLAYABLE;
-            }
-            /* set uid for current item */
-            byte[] uid = stringToByte(item.getDescription().getMediaId());
-            for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) {
-                folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx];
-            }
-
-            /* Set display name for current item */
-            folderDataNative.mDisplayNames[itemIndex] =
-                    getAttrValue(AvrcpConstants.ATTRID_TITLE, item);
-
-            int maxAttributesRequested = 0;
-            boolean isAllAttribRequested = false;
-            /* check if remote requested for attributes */
-            if (mFolderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
-                int attrCnt = 0;
-
-                /* add requested attr ids to a temp array */
-                if (mFolderItemsReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
-                    isAllAttribRequested = true;
-                    maxAttributesRequested = AvrcpConstants.MAX_NUM_ATTR;
-                } else {
-                    /* get only the requested attribute ids from the request */
-                    maxAttributesRequested = mFolderItemsReqObj.mNumAttr;
-                }
-
-                /* lookup and copy values of attributes for ids requested above */
-                for (int idx = 0; idx < maxAttributesRequested; idx++) {
-                    /* check if media player provided requested attributes */
-                    String value = null;
-
-                    int attribId =
-                            isAllAttribRequested ? (idx + 1) : mFolderItemsReqObj.mAttrIDs[idx];
-                    value = getAttrValue(attribId, resultItems.get(itemIndex));
-                    if (value != null) {
-                        attrArray.add(value);
-                        attrId.add(attribId);
-                        attrCnt++;
-                    }
-                }
-                /* add num attr actually received from media player for a particular item */
-                folderDataNative.mAttributesNum[itemIndex] = attrCnt;
-            }
-        }
-
-        /* copy filtered attr ids and attr values to response parameters */
-        if (attrId.size() > 0) {
-            folderDataNative.mAttrIds = new int[attrId.size()];
-            for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) {
-                folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex);
-            }
-            folderDataNative.mAttrValues = attrArray.toArray(new String[attrArray.size()]);
-        }
-
-        /* create rsp object and send response to remote device */
-        FolderItemsRsp rspObj =
-                new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR, Avrcp.sUIDCounter, scope,
-                        folderDataNative.mNumItems, folderDataNative.mFolderTypes,
-                        folderDataNative.mPlayable, folderDataNative.mItemTypes,
-                        folderDataNative.mItemUid, folderDataNative.mDisplayNames,
-                        folderDataNative.mAttributesNum, folderDataNative.mAttrIds,
-                        folderDataNative.mAttrValues);
-        mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
-    }
-
-    public static String getAttrValue(int attr, MediaBrowser.MediaItem item) {
-        String attrValue = null;
-        try {
-            MediaDescription desc = item.getDescription();
-            Bundle extras = desc.getExtras();
-            switch (attr) {
-                /* Title is mandatory attribute */
-                case AvrcpConstants.ATTRID_TITLE:
-                    attrValue = desc.getTitle().toString();
-                    break;
-
-                case AvrcpConstants.ATTRID_ARTIST:
-                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_ARTIST);
-                    break;
-
-                case AvrcpConstants.ATTRID_ALBUM:
-                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_ALBUM);
-                    break;
-
-                case AvrcpConstants.ATTRID_TRACK_NUM:
-                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
-                    break;
-
-                case AvrcpConstants.ATTRID_NUM_TRACKS:
-                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_NUM_TRACKS);
-                    break;
-
-                case AvrcpConstants.ATTRID_GENRE:
-                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_GENRE);
-                    break;
-
-                case AvrcpConstants.ATTRID_PLAY_TIME:
-                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_DURATION);
-                    break;
-
-                case AvrcpConstants.ATTRID_COVER_ART:
-                    Log.e(TAG, "getAttrValue: Cover art attribute not supported");
-                    return null;
-
-                default:
-                    Log.e(TAG, "getAttrValue: Unknown attribute ID requested: " + attr);
-                    return null;
-            }
-        } catch (NullPointerException ex) {
-            Log.w(TAG, "getAttrValue: attr id not found in result");
-            /* checking if attribute is title, then it is mandatory and cannot send null */
-            if (attr == AvrcpConstants.ATTRID_TITLE) {
-                attrValue = "<Unknown Title>";
-            } else {
-                return null;
-            }
-        }
-        if (DEBUG) {
-            Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + "attr id:" + attr);
-        }
-        return attrValue;
-    }
-
-
-    public String getPackageName() {
-        return mPackageName;
-    }
-
-    /* Helper methods */
-
-    /* check if item is browsable Down*/
-    private boolean isBrowsableFolderDn(String uid) {
-        for (MediaBrowser.MediaItem item : mFolderItems) {
-            if (item.getMediaId().equals(uid) && (
-                    (item.getFlags() & MediaBrowser.MediaItem.FLAG_BROWSABLE)
-                            == MediaBrowser.MediaItem.FLAG_BROWSABLE)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /* check if browsable Up*/
-    private boolean isBrowsableFolderUp() {
-        if (mPathStack.peek().equals(mRootFolderUid)) {
-            /* Already on the root, cannot go up */
-            return false;
-        }
-        return true;
-    }
-
-    /* convert uid to mediaId */
-    private String byteToString(byte[] byteArray) {
-        int uid = new BigInteger(byteArray).intValue();
-        String mediaId = mHmap.get(uid);
-        return mediaId;
-    }
-
-    /* convert mediaId to uid */
-    private byte[] stringToByte(String mediaId) {
-        /* 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);
-            return intToByteArray(uid);
-        } else { /* search key for give mediaId */
-            for (int uid : mHmap.keySet()) {
-                if (mHmap.get(uid).equals(mediaId)) {
-                    return intToByteArray(uid);
-                }
-            }
-        }
-        return null;
-    }
-
-    /* converts queue item received from getQueue call, to MediaItem used by FilterAttr method */
-    private List<MediaBrowser.MediaItem> queueItem2MediaItem(
-            List<MediaSession.QueueItem> tempItems) {
-
-        List<MediaBrowser.MediaItem> tempMedia = new ArrayList<MediaBrowser.MediaItem>();
-        for (int itemCount = 0; itemCount < tempItems.size(); itemCount++) {
-            MediaDescription.Builder build = new MediaDescription.Builder();
-            build.setMediaId(Long.toString(tempItems.get(itemCount).getQueueId()));
-            build.setTitle(tempItems.get(itemCount).getDescription().getTitle());
-            build.setExtras(tempItems.get(itemCount).getDescription().getExtras());
-            MediaDescription des = build.build();
-            MediaItem item = new MediaItem((des), MediaItem.FLAG_PLAYABLE);
-            tempMedia.add(item);
-        }
-        return tempMedia;
-    }
-
-    /* convert integer to byte array of size 8 bytes */
-    public byte[] intToByteArray(int value) {
-        int index = 0;
-        byte[] encodedValue = new byte[AvrcpConstants.UID_SIZE];
-
-        encodedValue[index++] = (byte) 0x00;
-        encodedValue[index++] = (byte) 0x00;
-        encodedValue[index++] = (byte) 0x00;
-        encodedValue[index++] = (byte) 0x00;
-        encodedValue[index++] = (byte) (value >> 24);
-        encodedValue[index++] = (byte) (value >> 16);
-        encodedValue[index++] = (byte) (value >> 8);
-        encodedValue[index++] = (byte) value;
-
-        return encodedValue;
-    }
-}
diff --git a/src/com/android/bluetooth/newavrcp/BrowsedPlayerWrapper.java b/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
similarity index 99%
rename from src/com/android/bluetooth/newavrcp/BrowsedPlayerWrapper.java
rename to src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
index 93afd17..b704aae 100644
--- a/src/com/android/bluetooth/newavrcp/BrowsedPlayerWrapper.java
+++ b/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
@@ -33,7 +33,7 @@
  * Right now this is ok because the BrowsablePlayerConnector will handle timeouts.
  */
 class BrowsedPlayerWrapper {
-    private static final String TAG = "NewAvrcpBrowsedPlayerWrapper";
+    private static final String TAG = "AvrcpBrowsedPlayerWrapper";
     private static final boolean DEBUG = true;
 
     enum ConnectionState {
diff --git a/src/com/android/bluetooth/newavrcp/GPMWrapper.java b/src/com/android/bluetooth/avrcp/GPMWrapper.java
similarity index 97%
rename from src/com/android/bluetooth/newavrcp/GPMWrapper.java
rename to src/com/android/bluetooth/avrcp/GPMWrapper.java
index a7f41fb..87e5ec0 100644
--- a/src/com/android/bluetooth/newavrcp/GPMWrapper.java
+++ b/src/com/android/bluetooth/avrcp/GPMWrapper.java
@@ -25,7 +25,7 @@
  * methods to allow Google Play Music to match the default behaviour of MediaPlayerWrapper.
  */
 class GPMWrapper extends MediaPlayerWrapper {
-    private static final String TAG = "NewAvrcpGPMWrapper";
+    private static final String TAG = "AvrcpGPMWrapper";
     private static final boolean DEBUG = true;
 
     @Override
diff --git a/src/com/android/bluetooth/newavrcp/MediaPlayerList.java b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
similarity index 96%
rename from src/com/android/bluetooth/newavrcp/MediaPlayerList.java
rename to src/com/android/bluetooth/avrcp/MediaPlayerList.java
index 74ea039..29f4cf9 100644
--- a/src/com/android/bluetooth/newavrcp/MediaPlayerList.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
@@ -57,7 +57,7 @@
  * player would effectively cause player switch by sending a play command to that player.
  */
 public class MediaPlayerList {
-    private static final String TAG = "NewAvrcpMediaPlayerList";
+    private static final String TAG = "AvrcpMediaPlayerList";
     private static final boolean DEBUG = true;
     static boolean sTesting = false;
 
@@ -89,6 +89,7 @@
     private int mActivePlayerId = NO_ACTIVE_PLAYER;
 
     private AvrcpTargetService.ListCallback mCallback;
+    private BrowsablePlayerConnector mBrowsablePlayerConnector;
 
     interface MediaUpdateCallback {
         void run(MediaData data);
@@ -140,8 +141,8 @@
                     .getPackageManager()
                     .queryIntentServices(intent, PackageManager.MATCH_ALL);
 
-        BrowsablePlayerConnector.connectToPlayers(mContext, mLooper, playerList,
-                (List<BrowsedPlayerWrapper> players) -> {
+        mBrowsablePlayerConnector = BrowsablePlayerConnector.connectToPlayers(mContext, mLooper,
+                playerList, (List<BrowsedPlayerWrapper> players) -> {
                 Log.i(TAG, "init: Browsable Player list size is " + players.size());
 
                 // Check to see if the list has been cleaned up before this completed
@@ -196,6 +197,9 @@
         }
         mMediaPlayers.clear();
 
+        if (mBrowsablePlayerConnector != null) {
+            mBrowsablePlayerConnector.cleanup();
+        }
         for (BrowsedPlayerWrapper player : mBrowsablePlayers.values()) {
             player.disconnect();
         }
@@ -207,8 +211,10 @@
     }
 
     int getFreeMediaPlayerId() {
-        int id = 0;
-        while (mMediaPlayerIds.containsValue(++id)) {}
+        int id = 1;
+        while (mMediaPlayerIds.containsValue(id)) {
+            id++;
+        }
         return id;
     }
 
@@ -620,6 +626,12 @@
                     android.media.session.MediaController controller =
                             new android.media.session.MediaController(mContext, token);
 
+                    if (mMediaSessionManager == null) {
+                        Log.w(TAG, "onAddressedPlayerChanged(Token): Unexpected callback "
+                                + "from the MediaSessionManager");
+                        return;
+                    }
+
                     if (!mMediaPlayerIds.containsKey(controller.getPackageName())) {
                         // Since we have a controller, we can try to to recover by adding the
                         // player and then setting it as active.
@@ -634,6 +646,12 @@
 
                 @Override
                 public void onAddressedPlayerChanged(ComponentName receiver) {
+                    if (mMediaSessionManager == null) {
+                        Log.w(TAG, "onAddressedPlayerChanged(Component): Unexpected callback "
+                                + "from the MediaSessionManager");
+                        return;
+                    }
+
                     if (receiver == null) {
                         return;
                     }
diff --git a/src/com/android/bluetooth/newavrcp/MediaPlayerWrapper.java b/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
similarity index 95%
rename from src/com/android/bluetooth/newavrcp/MediaPlayerWrapper.java
rename to src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
index 256e771..e4176b3 100644
--- a/src/com/android/bluetooth/newavrcp/MediaPlayerWrapper.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
@@ -23,10 +23,11 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.support.annotation.GuardedBy;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.List;
 import java.util.Objects;
 
@@ -38,8 +39,8 @@
  * with that.
  */
 class MediaPlayerWrapper {
-    private static final String TAG = "NewAvrcpMediaPlayerWrapper";
-    private static final boolean DEBUG = true;
+    private static final String TAG = "AvrcpMediaPlayerWrapper";
+    private static final boolean DEBUG = false;
     static boolean sTesting = false;
 
     private MediaController mMediaController;
@@ -61,14 +62,18 @@
         void mediaUpdatedCallback(MediaData data);
     }
 
-    boolean isReady() {
+    boolean isPlaybackStateReady() {
         if (getPlaybackState() == null) {
-            d("isReady(): PlaybackState is null");
+            d("isPlaybackStateReady(): PlaybackState is null");
             return false;
         }
 
+        return true;
+    }
+
+    boolean isMetadataReady() {
         if (getMetadata() == null) {
-            d("isReady(): Metadata is null");
+            d("isMetadataReady(): Metadata is null");
             return false;
         }
 
@@ -371,13 +376,16 @@
 
         @Override
         public void onMetadataChanged(@Nullable MediaMetadata metadata) {
-            if (!isReady()) {
+            if (!isMetadataReady()) {
                 Log.v(TAG, "onMetadataChanged(): " + mPackageName
                         + " tried to update with no queue");
                 return;
             }
 
-            Log.v(TAG, "onMetadataChanged(): " + mPackageName + " : " + Util.toMetadata(metadata));
+            if (DEBUG) {
+                Log.v(TAG, "onMetadataChanged(): " + mPackageName + " : "
+                    + Util.toMetadata(metadata));
+            }
 
             if (!Objects.equals(metadata, getMetadata())) {
                 e("The callback metadata doesn't match controller metadata");
@@ -402,7 +410,7 @@
 
         @Override
         public void onPlaybackStateChanged(@Nullable PlaybackState state) {
-            if (!isReady()) {
+            if (!isPlaybackStateReady()) {
                 Log.v(TAG, "onPlaybackStateChanged(): " + mPackageName
                         + " tried to update with no queue");
                 return;
@@ -431,7 +439,7 @@
 
         @Override
         public void onQueueChanged(@Nullable List<MediaSession.QueueItem> queue) {
-            if (!isReady()) {
+            if (!isPlaybackStateReady() || !isMetadataReady()) {
                 Log.v(TAG, "onQueueChanged(): " + mPackageName
                         + " tried to update with no queue");
                 return;
diff --git a/src/com/android/bluetooth/newavrcp/helpers/AvrcpPassthrough.java b/src/com/android/bluetooth/avrcp/helpers/AvrcpPassthrough.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/AvrcpPassthrough.java
rename to src/com/android/bluetooth/avrcp/helpers/AvrcpPassthrough.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/Folder.java b/src/com/android/bluetooth/avrcp/helpers/Folder.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/Folder.java
rename to src/com/android/bluetooth/avrcp/helpers/Folder.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/ListItem.java b/src/com/android/bluetooth/avrcp/helpers/ListItem.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/ListItem.java
rename to src/com/android/bluetooth/avrcp/helpers/ListItem.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/MediaData.java b/src/com/android/bluetooth/avrcp/helpers/MediaData.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/MediaData.java
rename to src/com/android/bluetooth/avrcp/helpers/MediaData.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/Metadata.java b/src/com/android/bluetooth/avrcp/helpers/Metadata.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/Metadata.java
rename to src/com/android/bluetooth/avrcp/helpers/Metadata.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/PlayStatus.java b/src/com/android/bluetooth/avrcp/helpers/PlayStatus.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/PlayStatus.java
rename to src/com/android/bluetooth/avrcp/helpers/PlayStatus.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/PlayerInfo.java b/src/com/android/bluetooth/avrcp/helpers/PlayerInfo.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/PlayerInfo.java
rename to src/com/android/bluetooth/avrcp/helpers/PlayerInfo.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/Util.java b/src/com/android/bluetooth/avrcp/helpers/Util.java
similarity index 99%
rename from src/com/android/bluetooth/newavrcp/helpers/Util.java
rename to src/com/android/bluetooth/avrcp/helpers/Util.java
index eb84b0a..e09377b 100644
--- a/src/com/android/bluetooth/newavrcp/helpers/Util.java
+++ b/src/com/android/bluetooth/avrcp/helpers/Util.java
@@ -29,7 +29,7 @@
 import java.util.List;
 
 class Util {
-    public static String TAG = "NewAvrcpUtil";
+    public static String TAG = "AvrcpUtil";
     public static boolean DEBUG = false;
 
     private static final String GPM_KEY = "com.google.android.music.mediasession.music_metadata";
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaController.java b/src/com/android/bluetooth/avrcp/mockable/MediaController.java
index 934a454..5c7b73f 100644
--- a/src/com/android/bluetooth/avrcp/mockable/MediaController.java
+++ b/src/com/android/bluetooth/avrcp/mockable/MediaController.java
@@ -44,13 +44,13 @@
     public android.media.session.MediaController.TransportControls mTransportDelegate;
     public TransportControls mTransportControls;
 
-    public MediaController(@NonNull android.media.session.MediaController delegate) {
+    MediaController(@NonNull android.media.session.MediaController delegate) {
         mDelegate = delegate;
         mTransportDelegate = delegate.getTransportControls();
         mTransportControls = new TransportControls();
     }
 
-    public MediaController(Context context, MediaSession.Token token) {
+    MediaController(Context context, MediaSession.Token token) {
         mDelegate = new android.media.session.MediaController(context, token);
         mTransportDelegate = mDelegate.getTransportControls();
         mTransportControls = new TransportControls();
@@ -65,101 +65,169 @@
         return mTransportControls;
     }
 
+    /**
+     * Wrapper for MediaController.dispatchMediaButtonEvent(KeyEvent keyEvent)
+     */
     public boolean dispatchMediaButtonEvent(@NonNull KeyEvent keyEvent) {
         return mDelegate.dispatchMediaButtonEvent(keyEvent);
     }
 
+    /**
+     * Wrapper for MediaController.getPlaybackState()
+     */
     @Nullable
     public PlaybackState getPlaybackState() {
         return mDelegate.getPlaybackState();
     }
 
+
+    /**
+     * Wrapper for MediaController.getMetadata()
+     */
     @Nullable
     public MediaMetadata getMetadata() {
         return mDelegate.getMetadata();
     }
 
+    /**
+     * Wrapper for MediaController.getQueue()
+     */
     @Nullable
     public List<MediaSession.QueueItem> getQueue() {
         return mDelegate.getQueue();
     }
 
+    /**
+     * Wrapper for MediaController.getQueueTitle()
+     */
     @Nullable
     public CharSequence getQueueTitle() {
         return mDelegate.getQueueTitle();
     }
 
+    /**
+     * Wrapper for MediaController.getExtras()
+     */
     @Nullable
     public Bundle getExtras() {
         return mDelegate.getExtras();
     }
 
+    /**
+     * Wrapper for MediaController.getRatingType()
+     */
     public int getRatingType() {
         return mDelegate.getRatingType();
     }
 
+    /**
+     * Wrapper for MediaController.getFlags()
+     */
     public long getFlags() {
         return mDelegate.getFlags();
     }
 
+    /**
+     * Wrapper for MediaController.getPlaybackInfo()
+     */
     @Nullable
     public android.media.session.MediaController.PlaybackInfo getPlaybackInfo() {
         return mDelegate.getPlaybackInfo();
     }
 
+
+    /**
+     * Wrapper for MediaController.getSessionActivity()
+     */
     @Nullable
     public PendingIntent getSessionActivity() {
         return mDelegate.getSessionActivity();
     }
 
+    /**
+     * Wrapper for MediaController.getSessionToken()
+     */
     @NonNull
     public MediaSession.Token getSessionToken() {
         return mDelegate.getSessionToken();
     }
 
+    /**
+     * Wrapper for MediaController.setVolumeTo(int value, int flags)
+     */
     public void setVolumeTo(int value, int flags) {
         mDelegate.setVolumeTo(value, flags);
     }
 
+    /**
+     * Wrapper for MediaController.adjustVolume(int direction, int flags)
+     */
     public void adjustVolume(int direction, int flags) {
         mDelegate.adjustVolume(direction, flags);
     }
 
+    /**
+     * Wrapper for MediaController.registerCallback(Callback callback)
+     */
     public void registerCallback(@NonNull Callback callback) {
         //TODO(apanicke): Add custom callback struct to be able to analyze and
         // delegate callbacks
         mDelegate.registerCallback(callback);
     }
 
+    /**
+     * Wrapper for MediaController.registerCallback(Callback callback, Handler handler)
+     */
     public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
         mDelegate.registerCallback(callback, handler);
     }
 
+    /**
+     * Wrapper for MediaController.unregisterCallback(Callback callback)
+     */
     public void unregisterCallback(@NonNull Callback callback) {
         mDelegate.unregisterCallback(callback);
     }
 
+    /**
+     * Wrapper for MediaController.sendCommand(String command, Bundle args, ResultReceiver cb)
+     */
     public void sendCommand(@NonNull String command, @Nullable Bundle args,
             @Nullable ResultReceiver cb) {
         mDelegate.sendCommand(command, args, cb);
     }
 
+    /**
+     * Wrapper for MediaController.getPackageName()
+     */
     public String getPackageName() {
         return mDelegate.getPackageName();
     }
 
+    /**
+     * Wrapper for MediaController.getTag()
+     */
     public String getTag() {
         return mDelegate.getTag();
     }
 
+    /**
+     * Wrapper for MediaController.controlsSameSession(MediaController other)
+     */
     public boolean controlsSameSession(MediaController other) {
         return mDelegate.controlsSameSession(other.getWrappedInstance());
     }
 
+    /**
+     * Wrapper for MediaController.controlsSameSession(MediaController other)
+     */
     public boolean controlsSameSession(android.media.session.MediaController other) {
         return mDelegate.controlsSameSession(other);
     }
 
+    /**
+     * Wrapper for MediaController.equals(Object other)
+     */
     @Override
     public boolean equals(Object o) {
         if (o instanceof android.media.session.MediaController) {
@@ -171,6 +239,9 @@
         return false;
     }
 
+    /**
+     * Wrapper for MediaController.toString()
+     */
     @Override
     public String toString() {
         MediaMetadata data = getMetadata();
@@ -179,83 +250,146 @@
                 mDelegate.hashCode()) + ") " + desc;
     }
 
+    /**
+     * Wrapper for MediaController.Callback
+     */
     public abstract static class Callback extends android.media.session.MediaController.Callback {}
 
+    /**
+     * Wrapper for MediaController.TransportControls
+     */
     public class TransportControls {
 
+        /**
+         * Wrapper for MediaController.TransportControls.prepare()
+         */
         public void prepare() {
             mTransportDelegate.prepare();
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.prepareFromMediaId()
+         */
         public void prepareFromMediaId(String mediaId, Bundle extras) {
             mTransportDelegate.prepareFromMediaId(mediaId, extras);
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.prepareFromSearch()
+         */
         public void prepareFromSearch(String query, Bundle extras) {
             mTransportDelegate.prepareFromSearch(query, extras);
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.prepareFromUri()
+         */
         public void prepareFromUri(Uri uri, Bundle extras) {
             mTransportDelegate.prepareFromUri(uri, extras);
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.play()
+         */
         public void play() {
             mTransportDelegate.play();
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.playFromMediaId()
+         */
         public void playFromMediaId(String mediaId, Bundle extras) {
             mTransportDelegate.playFromMediaId(mediaId, extras);
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.playFromSearch()
+         */
         public void playFromSearch(String query, Bundle extras) {
             mTransportDelegate.playFromSearch(query, extras);
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.playFromUri()
+         */
         public void playFromUri(Uri uri, Bundle extras) {
             mTransportDelegate.playFromUri(uri, extras);
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.skipToQueueItem()
+         */
         public void skipToQueueItem(long id) {
             mTransportDelegate.skipToQueueItem(id);
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.pause()
+         */
         public void pause() {
             mTransportDelegate.pause();
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.stop()
+         */
         public void stop() {
             mTransportDelegate.stop();
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.seekTo()
+         */
         public void seekTo(long pos) {
             mTransportDelegate.seekTo(pos);
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.fastForward()
+         */
         public void fastForward() {
             mTransportDelegate.fastForward();
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.skipToNext()
+         */
         public void skipToNext() {
             mTransportDelegate.skipToNext();
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.rewind()
+         */
         public void rewind() {
             mTransportDelegate.rewind();
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.skipToPrevious()
+         */
         public void skipToPrevious() {
             mTransportDelegate.skipToPrevious();
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.setRating()
+         */
         public void setRating(Rating rating) {
             mTransportDelegate.setRating(rating);
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.sendCustomAction()
+         */
         public void sendCustomAction(@NonNull PlaybackState.CustomAction customAction,
                 @Nullable Bundle args) {
             mTransportDelegate.sendCustomAction(customAction, args);
         }
 
+        /**
+         * Wrapper for MediaController.TransportControls.sendCustomAction()
+         */
         public void sendCustomAction(@NonNull String action, @Nullable Bundle args) {
             mTransportDelegate.sendCustomAction(action, args);
         }
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index 6111ec4..693b4a2 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -21,14 +21,11 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothAvrcpController;
+import android.content.Intent;
 import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.media.browse.MediaBrowser;
 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.util.Log;
 
 import com.android.bluetooth.Utils;
@@ -37,15 +34,21 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * 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 int MAXIMUM_CONNECTED_DEVICES = 5;
+    static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+    static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
+
+    public static final String MEDIA_ITEM_UID_KEY = "media-item-uid-key";
     /*
      *  Play State Values from JNI
      */
@@ -56,97 +59,19 @@
     private static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04;
     private static final byte JNI_PLAY_STATUS_ERROR = -1;
 
-    /*
-     * Browsing Media Item Attribute IDs
-     * This should be kept in sync with BTRC_MEDIA_ATTR_ID_* in bt_rc.h
+    /* Folder/Media Item scopes.
+     * Keep in sync with AVRCP 1.6 sec. 6.10.1
      */
-    private static final int JNI_MEDIA_ATTR_ID_INVALID = -1;
-    private static final int JNI_MEDIA_ATTR_ID_TITLE = 0x00000001;
-    private static final int JNI_MEDIA_ATTR_ID_ARTIST = 0x00000002;
-    private static final int JNI_MEDIA_ATTR_ID_ALBUM = 0x00000003;
-    private static final int JNI_MEDIA_ATTR_ID_TRACK_NUM = 0x00000004;
-    private static final int JNI_MEDIA_ATTR_ID_NUM_TRACKS = 0x00000005;
-    private static final int JNI_MEDIA_ATTR_ID_GENRE = 0x00000006;
-    private static final int JNI_MEDIA_ATTR_ID_PLAYING_TIME = 0x00000007;
+    public static final byte BROWSE_SCOPE_PLAYER_LIST = 0x00;
+    public static final byte BROWSE_SCOPE_VFS = 0x01;
+    public static final byte BROWSE_SCOPE_SEARCH = 0x02;
+    public static final byte BROWSE_SCOPE_NOW_PLAYING = 0x03;
 
-    /*
-     * Browsing folder types
-     * This should be kept in sync with BTRC_FOLDER_TYPE_* in bt_rc.h
+    /* Folder navigation directions
+     * This is borrowed from AVRCP 1.6 spec and must be kept with same values
      */
-    private static final int JNI_FOLDER_TYPE_TITLES = 0x01;
-    private static final int JNI_FOLDER_TYPE_ALBUMS = 0x02;
-    private static final int JNI_FOLDER_TYPE_ARTISTS = 0x03;
-    private static final int JNI_FOLDER_TYPE_GENRES = 0x04;
-    private static final int JNI_FOLDER_TYPE_PLAYLISTS = 0x05;
-    private static final int JNI_FOLDER_TYPE_YEARS = 0x06;
-
-    /*
-     * AVRCP Error types as defined in spec. Also they should be in sync with btrc_status_t.
-     * NOTE: Not all may be defined.
-     */
-    private static final int JNI_AVRC_STS_NO_ERROR = 0x04;
-    private static final int JNI_AVRC_INV_RANGE = 0x0b;
-
-    /**
-     * Intent used to broadcast the change in browse connection state of the AVRCP Controller
-     * profile.
-     *
-     * <p>This intent will have 2 extras:
-     * <ul>
-     *   <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li>
-     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
-     * </ul>
-     *
-     * <p>{@link #EXTRA_STATE} can be any of
-     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
-     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
-     */
-    public static final String ACTION_BROWSE_CONNECTION_STATE_CHANGED =
-            "android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED";
-
-    /**
-     * intent used to broadcast the change in metadata state of playing track on the avrcp
-     * ag.
-     *
-     * <p>this intent will have the two extras:
-     * <ul>
-     *    <li> {@link #extra_metadata} - {@link mediametadata} containing the current metadata.</li>
-     *    <li> {@link #extra_playback} - {@link playbackstate} containing the current playback
-     *    state. </li>
-     * </ul>
-     */
-    public static final String ACTION_TRACK_EVENT =
-            "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT";
-
-    /**
-     * Intent used to broadcast the change of folder list.
-     *
-     * <p>This intent will have the one extra:
-     * <ul>
-     *    <li> {@link #EXTRA_FOLDER_LIST} - array of {@link MediaBrowser#MediaItem}
-     *    containing the folder listing of currently selected folder.
-     * </ul>
-     */
-    public static final String ACTION_FOLDER_LIST =
-            "android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST";
-
-    public static final String EXTRA_FOLDER_LIST =
-            "android.bluetooth.avrcp-controller.profile.extra.FOLDER_LIST";
-
-    public static final String EXTRA_FOLDER_ID = "com.android.bluetooth.avrcp.EXTRA_FOLDER_ID";
-    public static final String EXTRA_FOLDER_BT_ID =
-            "com.android.bluetooth.avrcp-controller.EXTRA_FOLDER_BT_ID";
-
-    public static final String EXTRA_METADATA =
-            "android.bluetooth.avrcp-controller.profile.extra.METADATA";
-
-    public static final String EXTRA_PLAYBACK =
-            "android.bluetooth.avrcp-controller.profile.extra.PLAYBACK";
-
-    public static final String MEDIA_ITEM_UID_KEY = "media-item-uid-key";
+    public static final byte FOLDER_NAVIGATION_DIRECTION_UP = 0x00;
+    public static final byte FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01;
 
     /*
      * KeyCoded for Pass Through Commands
@@ -165,461 +90,109 @@
     public static final int KEY_STATE_PRESSED = 0;
     public static final int KEY_STATE_RELEASED = 1;
 
-    /* Group Navigation Key Codes */
-    public static final int PASS_THRU_CMD_ID_NEXT_GRP = 0x00;
-    public static final int PASS_THRU_CMD_ID_PREV_GRP = 0x01;
+    static BrowseTree sBrowseTree;
+    private static AvrcpControllerService sService;
+    private final BluetoothAdapter mAdapter;
 
-    /* Folder navigation directions
-     * This is borrowed from AVRCP 1.6 spec and must be kept with same values
-     */
-    public static final int FOLDER_NAVIGATION_DIRECTION_UP = 0x00;
-    public static final int FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01;
-
-    /* Folder/Media Item scopes.
-     * Keep in sync with AVRCP 1.6 sec. 6.10.1
-     */
-    public static final int BROWSE_SCOPE_PLAYER_LIST = 0x00;
-    public static final int BROWSE_SCOPE_VFS = 0x01;
-    public static final int BROWSE_SCOPE_SEARCH = 0x02;
-    public static final int BROWSE_SCOPE_NOW_PLAYING = 0x03;
-
-    private AvrcpControllerStateMachine mAvrcpCtSm;
-    private static AvrcpControllerService sAvrcpControllerService;
-    // UID size is 8 bytes (AVRCP 1.6 spec)
-    private static final byte[] EMPTY_UID = {0, 0, 0, 0, 0, 0, 0, 0};
-
-    // We only support one device.
-    private BluetoothDevice mConnectedDevice = null;
-    // If browse is supported (only valid if mConnectedDevice != null).
-    private boolean mBrowseConnected = false;
-    // Caches the current browse folder. If this is null then root is the currently browsed folder
-    // (which also has no UID).
-    private String mCurrentBrowseFolderUID = null;
+    protected Map<BluetoothDevice, AvrcpControllerStateMachine> mDeviceStateMap =
+            new ConcurrentHashMap<>(1);
 
     static {
         classInitNative();
     }
 
     public AvrcpControllerService() {
-        initNative();
-    }
-
-    @Override
-    protected IProfileServiceBinder initBinder() {
-        return new BluetoothAvrcpControllerBinder(this);
+        super();
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
     }
 
     @Override
     protected boolean start() {
-        HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
-        thread.start();
-        mAvrcpCtSm = new AvrcpControllerStateMachine(this);
-        mAvrcpCtSm.start();
+        initNative();
+        sBrowseTree = new BrowseTree(null);
+        sService = this;
 
-        setAvrcpControllerService(this);
+        // Start the media browser service.
+        Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class);
+        startService(startIntent);
         return true;
     }
 
     @Override
     protected boolean stop() {
-        setAvrcpControllerService(null);
-        if (mAvrcpCtSm != null) {
-            mAvrcpCtSm.doQuit();
+        Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class);
+        stopService(stopIntent);
+        for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
+            stateMachine.quitNow();
         }
+
+        sService = null;
+        sBrowseTree = null;
         return true;
     }
 
-    //API Methods
-
-    public static synchronized AvrcpControllerService getAvrcpControllerService() {
-        if (sAvrcpControllerService == null) {
-            Log.w(TAG, "getAvrcpControllerService(): service is null");
-            return null;
-        }
-        if (!sAvrcpControllerService.isAvailable()) {
-            Log.w(TAG, "getAvrcpControllerService(): service is not available ");
-            return null;
-        }
-        return sAvrcpControllerService;
+    public static AvrcpControllerService getAvrcpControllerService() {
+        return sService;
     }
 
-    private static synchronized void setAvrcpControllerService(AvrcpControllerService instance) {
-        if (DBG) {
-            Log.d(TAG, "setAvrcpControllerService(): set to: " + instance);
-        }
-        sAvrcpControllerService = instance;
+    protected AvrcpControllerStateMachine newStateMachine(BluetoothDevice device) {
+        return new AvrcpControllerStateMachine(device, this);
     }
 
-    public synchronized List<BluetoothDevice> getConnectedDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
-        if (mConnectedDevice != null) {
-            devices.add(mConnectedDevice);
+    private void refreshContents(BrowseTree.BrowseNode node) {
+        if (node.mDevice == null) {
+            return;
         }
-        return devices;
+        AvrcpControllerStateMachine stateMachine = getStateMachine(node.mDevice);
+        if (stateMachine != null) {
+            stateMachine.requestContents(node);
+        }
     }
 
+    /*Java API*/
+
     /**
-     * This function only supports STATE_CONNECTED
+     * Get a List of MediaItems that are children of the specified media Id
+     *
+     * @param parentMediaId The player or folder to get the contents of
+     * @return List of Children if available, an empty list if there are none,
+     * or null if a search must be performed.
      */
-    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);
+    public synchronized List<MediaItem> getContents(String parentMediaId) {
+        if (DBG) Log.d(TAG, "getContents(" + parentMediaId + ")");
+
+        BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId);
+        if (requestedNode == null) {
+            for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
+                requestedNode = stateMachine.findNode(parentMediaId);
+                if (requestedNode != null) {
+                    Log.d(TAG, "Found a node");
+                    break;
+                }
             }
         }
-        return devices;
-    }
 
-    public synchronized int getConnectionState(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return (mConnectedDevice != 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);
-        if (device == null) {
-            Log.e(TAG, "sendGroupNavigationCmd device is null");
-        }
-
-        if (!(device.equals(mConnectedDevice))) {
-            Log.e(TAG, " Device does not match " + device + " connected " + mConnectedDevice);
-            return;
-        }
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        Message msg = mAvrcpCtSm.obtainMessage(
-                AvrcpControllerStateMachine.MESSAGE_SEND_GROUP_NAVIGATION_CMD,
-                keyCode, keyState, device);
-        mAvrcpCtSm.sendMessage(msg);
-    }
-
-    public synchronized void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
-        Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState);
-        if (device == null) {
-            Log.e(TAG, "sendPassThroughCmd Device is null");
-            return;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.w(TAG, " Device does not match device " + device + " conn " + mConnectedDevice);
-            return;
-        }
-
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        Message msg =
-                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_SEND_PASS_THROUGH_CMD,
-                        keyCode, keyState, device);
-        mAvrcpCtSm.sendMessage(msg);
-    }
-
-    public void startAvrcpUpdates() {
-        mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_START_METADATA_BROADCASTS)
-                .sendToTarget();
-    }
-
-    public void stopAvrcpUpdates() {
-        mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_STOP_METADATA_BROADCASTS)
-                .sendToTarget();
-    }
-
-    public synchronized MediaMetadata getMetaData(BluetoothDevice device) {
-        if (DBG) {
-            Log.d(TAG, "getMetaData");
-        }
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (device == null) {
-            Log.e(TAG, "getMetadata device is null");
+        if (requestedNode == null) {
+            if (DBG) Log.d(TAG, "Didn't find a node");
             return null;
+        } else {
+            if (!requestedNode.isCached()) {
+                if (DBG) Log.d(TAG, "node is not cached");
+                refreshContents(requestedNode);
+            }
+            if (DBG) Log.d(TAG, "Returning contents");
+            return requestedNode.getContents();
         }
-
-        if (!device.equals(mConnectedDevice)) {
-            return null;
-        }
-        return mAvrcpCtSm.getCurrentMetaData();
     }
 
-    public PlaybackState getPlaybackState(BluetoothDevice device) {
-        // Get the cached state by default.
-        return getPlaybackState(device, true);
-    }
-
-    // cached can be used to force a getPlaybackState command. Useful for PTS testing.
-    public synchronized PlaybackState getPlaybackState(BluetoothDevice device, boolean cached) {
-        if (DBG) {
-            Log.d(TAG, "getPlayBackState device = " + device);
-        }
-
-        if (device == null) {
-            Log.e(TAG, "getPlaybackState device is null");
-            return null;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "Device " + device + " does not match connected deivce " + mConnectedDevice);
-            return null;
-
-        }
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mAvrcpCtSm.getCurrentPlayBackState(cached);
-    }
-
-    public synchronized BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
-        if (DBG) {
-            Log.d(TAG, "getPlayerApplicationSetting ");
-        }
-
-        if (device == null) {
-            Log.e(TAG, "getPlayerSettings device is null");
-            return null;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "device " + device + " does not match connected device " + mConnectedDevice);
-            return null;
-        }
-
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        /* Do nothing */
-        return null;
-    }
-
-    public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
-        if (DBG) {
-            Log.d(TAG, "getPlayerApplicationSetting");
-        }
-
-        /* Do nothing */
-        return false;
-    }
-
-    /**
-     * Fetches the list of children for the parentID node.
-     *
-     * This function manages the overall tree for browsing structure.
-     *
-     * Arguments:
-     * device - Device to browse content for.
-     * parentMediaId - ID of the parent that we need to browse content for. Since most
-     * of the players are database unware, fetching a root invalidates all the children.
-     * start - number of item to start scanning from
-     * items - number of items to fetch
-     */
-    public synchronized boolean getChildren(BluetoothDevice device, String parentMediaId, int start,
-            int items) {
-        if (DBG) {
-            Log.d(TAG, "getChildren device = " + device + " parent " + parentMediaId);
-        }
-
-        if (device == null) {
-            Log.e(TAG, "getChildren device is null");
-            return false;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "getChildren device " + device + " does not match " + mConnectedDevice);
-            return false;
-        }
-
-        if (!mBrowseConnected) {
-            Log.e(TAG, "getChildren browse not yet connected");
-            return false;
-        }
-
-        if (!mAvrcpCtSm.isConnected()) {
-            return false;
-        }
-        mAvrcpCtSm.getChildren(parentMediaId, start, items);
-        return true;
-    }
-
-    public synchronized boolean getNowPlayingList(BluetoothDevice device, String id, int start,
-            int items) {
-        if (DBG) {
-            Log.d(TAG, "getNowPlayingList device = " + device + " start = " + start + "items = "
-                    + items);
-        }
-
-        if (device == null) {
-            Log.e(TAG, "getNowPlayingList device is null");
-            return false;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG,
-                    "getNowPlayingList device " + device + " does not match " + mConnectedDevice);
-            return false;
-        }
-
-        if (!mBrowseConnected) {
-            Log.e(TAG, "getNowPlayingList browse not yet connected");
-            return false;
-        }
-
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        Message msg =
-                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST,
-                        start, items, id);
-        mAvrcpCtSm.sendMessage(msg);
-        return true;
-    }
-
-    public synchronized boolean getFolderList(BluetoothDevice device, String id, int start,
-            int items) {
-        if (DBG) {
-            Log.d(TAG, "getFolderListing device = " + device + " start = " + start + "items = "
-                    + items);
-        }
-
-        if (device == null) {
-            Log.e(TAG, "getFolderListing device is null");
-            return false;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "getFolderListing device " + device + " does not match " + mConnectedDevice);
-            return false;
-        }
-
-        if (!mBrowseConnected) {
-            Log.e(TAG, "getFolderListing browse not yet connected");
-            return false;
-        }
-
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        Message msg =
-                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start,
-                        items, id);
-        mAvrcpCtSm.sendMessage(msg);
-        return true;
-    }
-
-    public synchronized boolean getPlayerList(BluetoothDevice device, int start, int items) {
-        if (DBG) {
-            Log.d(TAG,
-                    "getPlayerList device = " + device + " start = " + start + "items = " + items);
-        }
-
-        if (device == null) {
-            Log.e(TAG, "getPlayerList device is null");
-            return false;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "getPlayerList device " + device + " does not match " + mConnectedDevice);
-            return false;
-        }
-
-        if (!mBrowseConnected) {
-            Log.e(TAG, "getPlayerList browse not yet connected");
-            return false;
-        }
-
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        Message msg =
-                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start,
-                        items);
-        mAvrcpCtSm.sendMessage(msg);
-        return true;
-    }
-
-    public synchronized boolean changeFolderPath(BluetoothDevice device, int direction, String uid,
-            String fid) {
-        if (DBG) {
-            Log.d(TAG, "changeFolderPath device = " + device + " direction " + direction + " uid "
-                    + uid);
-        }
-
-        if (device == null) {
-            Log.e(TAG, "changeFolderPath device is null");
-            return false;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "changeFolderPath device " + device + " does not match " + mConnectedDevice);
-            return false;
-        }
-
-        if (!mBrowseConnected) {
-            Log.e(TAG, "changeFolderPath browse not yet connected");
-            return false;
-        }
-
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        Bundle b = new Bundle();
-        b.putString(EXTRA_FOLDER_ID, fid);
-        b.putString(EXTRA_FOLDER_BT_ID, uid);
-        Message msg =
-                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH,
-                        direction, 0, b);
-        mAvrcpCtSm.sendMessage(msg);
-        return true;
-    }
-
-    public synchronized boolean setBrowsedPlayer(BluetoothDevice device, int id, String fid) {
-        if (DBG) {
-            Log.d(TAG, "setBrowsedPlayer device = " + device + " id" + id + " fid " + fid);
-        }
-
-        if (device == null) {
-            Log.e(TAG, "setBrowsedPlayer device is null");
-            return false;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "changeFolderPath device " + device + " does not match " + mConnectedDevice);
-            return false;
-        }
-
-        if (!mBrowseConnected) {
-            Log.e(TAG, "setBrowsedPlayer browse not yet connected");
-            return false;
-        }
-
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        Message msg =
-                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, id,
-                        0, fid);
-        mAvrcpCtSm.sendMessage(msg);
-        return true;
-    }
-
-    public synchronized void fetchAttrAndPlayItem(BluetoothDevice device, String uid) {
-        if (DBG) {
-            Log.d(TAG, "fetchAttrAndPlayItem device = " + device + " uid " + uid);
-        }
-
-        if (device == null) {
-            Log.e(TAG, "fetchAttrAndPlayItem device is null");
-            return;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "fetchAttrAndPlayItem device " + device + " does not match "
-                    + mConnectedDevice);
-            return;
-        }
-
-        if (!mBrowseConnected) {
-            Log.e(TAG, "fetchAttrAndPlayItem browse not yet connected");
-            return;
-        }
-        mAvrcpCtSm.fetchAttrAndPlayItem(uid);
+    @Override
+    protected IProfileServiceBinder initBinder() {
+        return new AvrcpControllerServiceBinder(this);
     }
 
     //Binder object: Must be static class or memory leak may occur
-    private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
+    private static class AvrcpControllerServiceBinder extends IBluetoothAvrcpController.Stub
             implements IProfileServiceBinder {
-
         private AvrcpControllerService mService;
 
         private AvrcpControllerService getService() {
@@ -628,14 +201,14 @@
                 return null;
             }
 
-            if (mService != null && mService.isAvailable()) {
+            if (mService != null) {
                 return mService;
             }
             return null;
         }
 
-        BluetoothAvrcpControllerBinder(AvrcpControllerService svc) {
-            mService = svc;
+        AvrcpControllerServiceBinder(AvrcpControllerService service) {
+            mService = service;
         }
 
         @Override
@@ -667,153 +240,102 @@
             if (service == null) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
-
-            if (device == null) {
-                throw new IllegalStateException("Device cannot be null!");
-            }
-
             return service.getConnectionState(device);
         }
 
         @Override
         public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
-            Log.v(TAG, "Binder Call: sendGroupNavigationCmd");
-            AvrcpControllerService service = getService();
-            if (service == null) {
-                return;
-            }
+            Log.w(TAG, "sendGroupNavigationCmd not implemented");
+            return;
+        }
 
-            if (device == null) {
-                throw new IllegalStateException("Device cannot be null!");
-            }
-
-            service.sendGroupNavigationCmd(device, keyCode, keyState);
+        @Override
+        public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings settings) {
+            Log.w(TAG, "setPlayerApplicationSetting not implemented");
+            return false;
         }
 
         @Override
         public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
-            Log.v(TAG, "Binder Call: getPlayerApplicationSetting ");
-            AvrcpControllerService service = getService();
-            if (service == null) {
-                return null;
-            }
-
-            if (device == null) {
-                throw new IllegalStateException("Device cannot be null!");
-            }
-
-            return service.getPlayerSettings(device);
-        }
-
-        @Override
-        public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
-            Log.v(TAG, "Binder Call: setPlayerApplicationSetting ");
-            AvrcpControllerService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.setPlayerApplicationSetting(plAppSetting);
+            Log.w(TAG, "getPlayerSettings not implemented");
+            return null;
         }
     }
 
+
+    /* JNI API*/
     // Called by JNI when a passthrough key was received.
     private void handlePassthroughRsp(int id, int keyState, byte[] address) {
-        Log.d(TAG,
-                "passthrough response received as: key: " + id + " state: " + keyState + "address:"
-                        + address);
+        if (DBG) {
+            Log.d(TAG, "passthrough response received as: key: " + id
+                    + " state: " + keyState + "address:" + address);
+        }
     }
 
     private void handleGroupNavigationRsp(int id, int keyState) {
-        Log.d(TAG, "group navigation response received as: key: " + id + " state: " + keyState);
+        if (DBG) {
+            Log.d(TAG, "group navigation response received as: key: " + id + " state: "
+                    + keyState);
+        }
     }
 
     // Called by JNI when a device has connected or disconnected.
-    private synchronized void onConnectionStateChanged(boolean rcConnected, boolean brConnected,
-            byte[] address) {
+    private synchronized void onConnectionStateChanged(boolean remoteControlConnected,
+            boolean browsingConnected, byte[] address) {
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        Log.d(TAG, "onConnectionStateChanged " + rcConnected + " " + brConnected + device
-                + " conn device " + mConnectedDevice);
+        if (DBG) {
+            Log.d(TAG, "onConnectionStateChanged " + remoteControlConnected + " "
+                    + browsingConnected + 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
-                : BluetoothProfile.STATE_DISCONNECTED);
-        int newState = (rcConnected ? BluetoothProfile.STATE_CONNECTED
-                : BluetoothProfile.STATE_DISCONNECTED);
-
-        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);
-            mAvrcpCtSm.sendMessage(msg);
-        }
-
-        // Adjust the browse connection state. If RC is connected we should have already sent the
-        // connection status out.
-        if (rcConnected && brConnected) {
-            mBrowseConnected = true;
-            Message msg = mAvrcpCtSm.obtainMessage(
-                    AvrcpControllerStateMachine.MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE);
-            msg.arg1 = 1;
-            msg.obj = device;
-            mAvrcpCtSm.sendMessage(msg);
+        StackEvent event =
+                StackEvent.connectionStateChanged(remoteControlConnected, browsingConnected);
+        AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device);
+        if (remoteControlConnected || browsingConnected) {
+            stateMachine.connect(event);
+        } else {
+            stateMachine.disconnect();
         }
     }
 
     // Called by JNI to notify Avrcp of features supported by the Remote device.
     private void getRcFeatures(byte[] address, int features) {
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        Message msg =
-                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES,
-                        features, 0, device);
-        mAvrcpCtSm.sendMessage(msg);
+        /* Do Nothing. */
     }
 
     // Called by JNI
     private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
-              /* Do Nothing. */
+        /* Do Nothing. */
     }
 
     // Called by JNI when remote wants to receive absolute volume notifications.
     private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) {
-        Log.d(TAG, "handleRegisterNotificationAbsVol ");
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        if (device != null && !device.equals(mConnectedDevice)) {
-            Log.e(TAG, "handleRegisterNotificationAbsVol device not found " + address);
-            return;
+        if (DBG) {
+            Log.d(TAG, "handleRegisterNotificationAbsVol");
         }
-        Message msg = mAvrcpCtSm.obtainMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION,
-                (int) label, 0);
-        mAvrcpCtSm.sendMessage(msg);
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(
+                    AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION);
+        }
     }
 
     // Called by JNI when remote wants to set absolute volume.
     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)) {
-            Log.e(TAG, "handleSetAbsVolume device not found " + address);
-            return;
+        if (DBG) {
+            Log.d(TAG, "handleSetAbsVolume ");
         }
-        Message msg = mAvrcpCtSm.obtainMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
-        mAvrcpCtSm.sendMessage(msg);
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD,
+                    absVol);
+        }
     }
 
     // Called by JNI when a track changes and local AvrcpController is registered for updates.
@@ -822,24 +344,13 @@
         if (DBG) {
             Log.d(TAG, "onTrackChanged");
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        if (device != null && !device.equals(mConnectedDevice)) {
-            Log.e(TAG, "onTrackChanged device not found " + address);
-            return;
-        }
 
-        List<Integer> attrList = new ArrayList<>();
-        for (int attr : attributes) {
-            attrList.add(attr);
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED,
+                    TrackInfo.getMetadata(attributes, attribVals));
         }
-        List<String> attrValList = Arrays.asList(attribVals);
-        TrackInfo trackInfo = new TrackInfo(attrList, attrValList);
-        if (VDBG) {
-            Log.d(TAG, "onTrackChanged " + trackInfo);
-        }
-        Message msg = mAvrcpCtSm.obtainMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED, trackInfo);
-        mAvrcpCtSm.sendMessage(msg);
     }
 
     // Called by JNI periodically based upon timer to update play position
@@ -849,14 +360,12 @@
             Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
         }
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        if (device != null && !device.equals(mConnectedDevice)) {
-            Log.e(TAG, "onPlayPositionChanged not found device not found " + address);
-            return;
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(
+                    AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED,
+                    songLen, currSongPosition);
         }
-        Message msg = mAvrcpCtSm.obtainMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED,
-                songLen, currSongPosition);
-        mAvrcpCtSm.sendMessage(msg);
     }
 
     // Called by JNI on changes of play status
@@ -864,11 +373,6 @@
         if (DBG) {
             Log.d(TAG, "onPlayStatusChanged " + playStatus);
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        if (device != null && !device.equals(mConnectedDevice)) {
-            Log.e(TAG, "onPlayStatusChanged not found device not found " + address);
-            return;
-        }
         int playbackState = PlaybackState.STATE_NONE;
         switch (playStatus) {
             case JNI_PLAY_STATUS_STOPPED:
@@ -884,14 +388,17 @@
                 playbackState = PlaybackState.STATE_FAST_FORWARDING;
                 break;
             case JNI_PLAY_STATUS_REV_SEEK:
-                playbackState = PlaybackState.STATE_FAST_FORWARDING;
+                playbackState = PlaybackState.STATE_REWINDING;
                 break;
             default:
                 playbackState = PlaybackState.STATE_NONE;
         }
-        Message msg = mAvrcpCtSm.obtainMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
-        mAvrcpCtSm.sendMessage(msg);
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(
+                    AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
+        }
     }
 
     // Called by JNI to report remote Player's capabilities
@@ -901,13 +408,14 @@
             Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
         }
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        if (device != null && !device.equals(mConnectedDevice)) {
-            Log.e(TAG, "handlePlayerAppSetting not found device not found " + address);
-            return;
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+            PlayerApplicationSettings supportedSettings =
+                    PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp);
+            stateMachine.sendMessage(
+                    AvrcpControllerStateMachine.MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS,
+                    supportedSettings);
         }
-        PlayerApplicationSettings supportedSettings =
-                PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp);
-        /* Do nothing */
     }
 
     private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp,
@@ -916,50 +424,50 @@
             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);
-            return;
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+
+            PlayerApplicationSettings currentSettings =
+                    PlayerApplicationSettings.makeSettings(playerAttribRsp);
+            stateMachine.sendMessage(
+                    AvrcpControllerStateMachine.MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS,
+                    currentSettings);
         }
-        PlayerApplicationSettings desiredSettings =
-                PlayerApplicationSettings.makeSettings(playerAttribRsp);
-        /* Do nothing */
     }
 
     // Browsing related JNI callbacks.
-    void handleGetFolderItemsRsp(int status, MediaItem[] items) {
+    void handleGetFolderItemsRsp(byte[] address, int status, MediaItem[] items) {
         if (DBG) {
             Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + " items "
                     + items.length + " items.");
         }
-
-        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
-            // to take as a signal that fetch is finished.
-            Message msg = mAvrcpCtSm.obtainMessage(
-                    AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
-            mAvrcpCtSm.sendMessage(msg);
-            return;
-        }
-
         for (MediaItem item : items) {
             if (VDBG) {
-                Log.d(TAG, "media item: " + item + " uid: " + item.getDescription().getMediaId());
+                Log.d(TAG, "media item: " + item + " uid: "
+                        + item.getDescription().getMediaId());
             }
         }
         ArrayList<MediaItem> itemsList = new ArrayList<>();
         for (MediaItem item : items) {
             itemsList.add(item);
         }
-        Message msg = mAvrcpCtSm.obtainMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS, itemsList);
-        mAvrcpCtSm.sendMessage(msg);
+
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+
+            stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS,
+                    itemsList);
+        }
     }
 
-    void handleGetPlayerItemsRsp(AvrcpPlayer[] items) {
+
+    void handleGetPlayerItemsRsp(byte[] address, AvrcpPlayer[] items) {
         if (DBG) {
             Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
         }
+
         for (AvrcpPlayer item : items) {
             if (VDBG) {
                 Log.d(TAG, "bt player item: " + item);
@@ -970,28 +478,31 @@
             itemsList.add(p);
         }
 
-        Message msg = mAvrcpCtSm.obtainMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS, itemsList);
-        mAvrcpCtSm.sendMessage(msg);
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
+                    itemsList);
+        }
     }
 
     // JNI Helper functions to convert native objects to java.
-    MediaItem createFromNativeMediaItem(byte[] uid, int type, String name, int[] attrIds,
+    MediaItem createFromNativeMediaItem(long uid, int type, String name, int[] attrIds,
             String[] attrVals) {
         if (VDBG) {
-            Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name " + name
-                    + " attrids " + attrIds + " attrVals " + attrVals);
+            Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name "
+                    + name + " attrids " + attrIds + " attrVals " + attrVals);
         }
         MediaDescription.Builder mdb = new MediaDescription.Builder();
 
         Bundle mdExtra = new Bundle();
-        mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid));
+        mdExtra.putLong(MEDIA_ITEM_UID_KEY, uid);
         mdb.setExtras(mdExtra);
 
+
         // Generate a random UUID. We do this since database unaware TGs can send multiple
         // items with same MEDIA_ITEM_UID_KEY.
         mdb.setMediaId(UUID.randomUUID().toString());
-
         // Concise readable name.
         mdb.setTitle(name);
 
@@ -1001,23 +512,20 @@
         return new MediaItem(mdb.build(), MediaItem.FLAG_PLAYABLE);
     }
 
-    MediaItem createFromNativeFolderItem(byte[] uid, int type, String name, int playable) {
+    MediaItem createFromNativeFolderItem(long uid, int type, String name, int playable) {
         if (VDBG) {
-            Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type + " name " + name
-                    + " playable " + playable);
+            Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type + " name "
+                    + name + " playable " + playable);
         }
         MediaDescription.Builder mdb = new MediaDescription.Builder();
 
-        // Covert the byte to a hex string. The coversion can be done back here to a
-        // byte array when needed.
         Bundle mdExtra = new Bundle();
-        mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid));
+        mdExtra.putLong(MEDIA_ITEM_UID_KEY, uid);
         mdb.setExtras(mdExtra);
 
         // Generate a random UUID. We do this since database unaware TGs can send multiple
         // items with same MEDIA_ITEM_UID_KEY.
         mdb.setMediaId(UUID.randomUUID().toString());
-
         // Concise readable name.
         mdb.setTitle(name);
 
@@ -1028,112 +536,285 @@
             int playStatus, int playerType) {
         if (VDBG) {
             Log.d(TAG,
-                    "createFromNativePlayerItem name: " + name + " transportFlags " + transportFlags
-                            + " play status " + playStatus + " player type " + playerType);
+                    "createFromNativePlayerItem name: " + name + " transportFlags "
+                            + transportFlags + " play status " + playStatus + " player type "
+                            + playerType);
         }
-        AvrcpPlayer player = new AvrcpPlayer(id, name, 0, playStatus, playerType);
+        AvrcpPlayer player = new AvrcpPlayer(id, name, transportFlags, playStatus, playerType);
         return player;
     }
 
-    private void handleChangeFolderRsp(int count) {
+    private void handleChangeFolderRsp(byte[] address, int count) {
         if (DBG) {
             Log.d(TAG, "handleChangeFolderRsp count: " + count);
         }
-        Message msg =
-                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
-                        count);
-        mAvrcpCtSm.sendMessage(msg);
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
+                    count);
+        }
     }
 
-    private void handleSetBrowsedPlayerRsp(int items, int depth) {
+    private void handleSetBrowsedPlayerRsp(byte[] address, int items, int depth) {
         if (DBG) {
             Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
         }
-        Message msg = mAvrcpCtSm.obtainMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth);
-        mAvrcpCtSm.sendMessage(msg);
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER,
+                    items, depth);
+        }
     }
 
-    private void handleSetAddressedPlayerRsp(int status) {
+    private void handleSetAddressedPlayerRsp(byte[] address, int status) {
         if (DBG) {
             Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
         }
-        Message msg = mAvrcpCtSm.obtainMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
-        mAvrcpCtSm.sendMessage(msg);
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(
+                    AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
+        }
+    }
+
+    private void handleAddressedPlayerChanged(byte[] address, int id) {
+        if (DBG) {
+            Log.d(TAG, "handleAddressedPlayerChanged id: " + id);
+        }
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(
+                    AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, id);
+        }
+    }
+
+    private void handleNowPlayingContentChanged(byte[] address) {
+        if (DBG) {
+            Log.d(TAG, "handleNowPlayingContentChanged");
+        }
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+            stateMachine.nowPlayingContentChanged();
+        }
+    }
+
+    /* Generic Profile Code */
+
+    /**
+     * Disconnect the given Bluetooth device.
+     *
+     * @return true if disconnect is successful, false otherwise.
+     */
+    public synchronized boolean disconnect(BluetoothDevice device) {
+        if (DBG) {
+            StringBuilder sb = new StringBuilder();
+            dump(sb);
+            Log.d(TAG, "MAP disconnect device: " + device
+                    + ", InstanceMap start state: " + sb.toString());
+        }
+        AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
+        // a map state machine instance doesn't exist. maybe it is already gone?
+        if (stateMachine == null) {
+            return false;
+        }
+        int connectionState = stateMachine.getState();
+        if (connectionState != BluetoothProfile.STATE_CONNECTED
+                && connectionState != BluetoothProfile.STATE_CONNECTING) {
+            return false;
+        }
+        stateMachine.disconnect();
+        if (DBG) {
+            StringBuilder sb = new StringBuilder();
+            dump(sb);
+            Log.d(TAG, "MAP disconnect device: " + device
+                    + ", InstanceMap start state: " + sb.toString());
+        }
+        return true;
+    }
+
+    /**
+     * Remove state machine from device map once it is no longer needed.
+     */
+    public void removeStateMachine(AvrcpControllerStateMachine stateMachine) {
+        mDeviceStateMap.remove(stateMachine.getDevice());
+    }
+
+    public List<BluetoothDevice> getConnectedDevices() {
+        return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
+    }
+
+    protected AvrcpControllerStateMachine getStateMachine(BluetoothDevice device) {
+        return mDeviceStateMap.get(device);
+    }
+
+    protected AvrcpControllerStateMachine getOrCreateStateMachine(BluetoothDevice device) {
+        AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
+        if (stateMachine == null) {
+            stateMachine = newStateMachine(device);
+            mDeviceStateMap.put(device, stateMachine);
+            stateMachine.start();
+        }
+        return stateMachine;
+    }
+
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
+        List<BluetoothDevice> deviceList = new ArrayList<>();
+        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+        int connectionState;
+        for (BluetoothDevice device : bondedDevices) {
+            connectionState = getConnectionState(device);
+            if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState);
+            for (int i = 0; i < states.length; i++) {
+                if (connectionState == states[i]) {
+                    deviceList.add(device);
+                }
+            }
+        }
+        if (DBG) Log.d(TAG, deviceList.toString());
+        Log.d(TAG, "GetDevicesDone");
+        return deviceList;
+    }
+
+    synchronized int getConnectionState(BluetoothDevice device) {
+        AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
+        return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED
+                : stateMachine.getState();
     }
 
     @Override
     public void dump(StringBuilder sb) {
         super.dump(sb);
-        mAvrcpCtSm.dump(sb);
+        ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size());
+
+        for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
+            ProfileService.println(sb,
+                    "==== StateMachine for " + stateMachine.getDevice() + " ====");
+            stateMachine.dump(sb);
+        }
+        sb.append("\n  sBrowseTree: " + sBrowseTree.toString());
     }
 
-    public static String byteUIDToHexString(byte[] uid) {
-        StringBuilder sb = new StringBuilder();
-        for (byte b : uid) {
-            sb.append(String.format("%02X", b));
-        }
-        return sb.toString();
-    }
-
-    public static byte[] hexStringToByteUID(String uidStr) {
-        if (uidStr == null) {
-            Log.e(TAG, "Null hex string.");
-            return EMPTY_UID;
-        } else if (uidStr.length() % 2 == 1) {
-            // Odd length strings should not be possible.
-            Log.e(TAG, "Odd length hex string " + uidStr);
-            return EMPTY_UID;
-        }
-        int len = uidStr.length();
-        byte[] data = new byte[len / 2];
-        for (int i = 0; i < len; i += 2) {
-            data[i / 2] = (byte) ((Character.digit(uidStr.charAt(i), 16) << 4) + Character.digit(
-                    uidStr.charAt(i + 1), 16));
-        }
-        return data;
-    }
-
+    /*JNI*/
     private static native void classInitNative();
 
     private native void initNative();
 
     private native void cleanupNative();
 
-    static native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
+    /**
+     * Send button press commands to addressed device
+     *
+     * @param keyCode  key code as defined in AVRCP specification
+     * @param keyState 0 = key pressed, 1 = key released
+     * @return command was sent
+     */
+    public native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
 
-    static native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
+    /**
+     * Send group navigation commands
+     *
+     * @param keyCode  next/previous
+     * @param keyState state
+     * @return command was sent
+     */
+    public native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
             int keyState);
 
-    static native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
-            byte[] atttibIds, byte[] attribVal);
+    /**
+     * Change player specific settings such as shuffle
+     *
+     * @param numAttrib number of settings being sent
+     * @param attribIds list of settings to be changed
+     * @param attribVal list of settings values
+     */
+    public native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
+            byte[] attribIds, byte[] attribVal);
 
-    /* This api is used to send response to SET_ABS_VOL_CMD */
-    static native void sendAbsVolRspNative(byte[] address, int absVol, int label);
+    /**
+     * Send response to set absolute volume
+     *
+     * @param absVol new volume
+     * @param label  label
+     */
+    public native void sendAbsVolRspNative(byte[] address, int absVol, int label);
 
-    /* This api is used to inform remote for any volume level changes */
-    static native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
+    /**
+     * Register for any volume level changes
+     *
+     * @param rspType type of response
+     * @param absVol  current volume
+     * @param label   label
+     */
+    public native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
             int label);
 
-    /* API used to fetch the playback state */
-    static native void getPlaybackStateNative(byte[] address);
+    /**
+     * Fetch the playback state
+     */
+    public native void getPlaybackStateNative(byte[] address);
 
-    /* API used to fetch the current now playing list */
-    static native void getNowPlayingListNative(byte[] address, int start, int end);
+    /**
+     * Fetch the current now playing list
+     *
+     * @param start first index to retrieve
+     * @param end   last index to retrieve
+     */
+    public native void getNowPlayingListNative(byte[] address, int start, int end);
 
-    /* API used to fetch the current folder's listing */
-    static native void getFolderListNative(byte[] address, int start, int end);
+    /**
+     * Fetch the current folder's listing
+     *
+     * @param start first index to retrieve
+     * @param end   last index to retrieve
+     */
+    public native void getFolderListNative(byte[] address, int start, int end);
 
-    /* API used to fetch the listing of players */
-    static native void getPlayerListNative(byte[] address, int start, int end);
+    /**
+     * Fetch the listing of players
+     *
+     * @param start first index to retrieve
+     * @param end   last index to retrieve
+     */
+    public native void getPlayerListNative(byte[] address, int start, int end);
 
-    /* API used to change the folder */
-    static native void changeFolderPathNative(byte[] address, byte direction, byte[] uid);
+    /**
+     * Change the current browsed folder
+     *
+     * @param direction up/down
+     * @param uid       folder unique id
+     */
+    public native void changeFolderPathNative(byte[] address, byte direction, long uid);
 
-    static native void playItemNative(byte[] address, byte scope, byte[] uid, int uidCounter);
+    /**
+     * Play item with provided uid
+     *
+     * @param scope      scope of item to played
+     * @param uid        song unique id
+     * @param uidCounter counter
+     */
+    public native void playItemNative(byte[] address, byte scope, long uid, int uidCounter);
 
-    static native void setBrowsedPlayerNative(byte[] address, int playerId);
+    /**
+     * Set a specific player for browsing
+     *
+     * @param playerId player number
+     */
+    public native void setBrowsedPlayerNative(byte[] address, int playerId);
 
-    static native void setAddressedPlayerNative(byte[] address, int playerId);
+    /**
+     * Set a specific player for handling playback commands
+     *
+     * @param playerId player number
+     */
+    public native void setAddressedPlayerNative(byte[] address, int playerId);
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index 3077664..83ba850 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -17,23 +17,22 @@
 package com.android.bluetooth.avrcpcontroller;
 
 import android.bluetooth.BluetoothAvrcpController;
-import android.bluetooth.BluetoothAvrcpPlayerSettings;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.media.AudioManager;
-import android.media.MediaDescription;
 import android.media.MediaMetadata;
 import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.PlaybackState;
 import android.os.Bundle;
 import android.os.Message;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.a2dpsink.A2dpSinkService;
 import com.android.bluetooth.btservice.MetricsLogger;
@@ -49,53 +48,47 @@
  * and interactions with a remote controlable device.
  */
 class AvrcpControllerStateMachine extends StateMachine {
+    static final String TAG = "AvrcpControllerStateMachine";
+    static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
-    // commands from Binder service
-    static final int MESSAGE_SEND_PASS_THROUGH_CMD = 1;
-    static final int MESSAGE_SEND_GROUP_NAVIGATION_CMD = 3;
-    static final int MESSAGE_GET_NOW_PLAYING_LIST = 5;
-    static final int MESSAGE_GET_FOLDER_LIST = 6;
-    static final int MESSAGE_GET_PLAYER_LIST = 7;
-    static final int MESSAGE_CHANGE_FOLDER_PATH = 8;
-    static final int MESSAGE_FETCH_ATTR_AND_PLAY_ITEM = 9;
-    static final int MESSAGE_SET_BROWSED_PLAYER = 10;
+    //0->99 Events from Outside
+    public static final int CONNECT = 1;
+    public static final int DISCONNECT = 2;
 
-    // commands from native layer
-    static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 103;
-    static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 104;
-    static final int MESSAGE_PROCESS_TRACK_CHANGED = 105;
-    static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 106;
-    static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 107;
-    static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 108;
-    static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 109;
-    static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE = 110;
-    static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 111;
-    static final int MESSAGE_PROCESS_FOLDER_PATH = 112;
-    static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 113;
-    static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 114;
+    //100->199 Internal Events
+    protected static final int CLEANUP = 100;
+    private static final int CONNECT_TIMEOUT = 101;
 
-    // commands from A2DP sink
-    static final int MESSAGE_STOP_METADATA_BROADCASTS = 201;
-    static final int MESSAGE_START_METADATA_BROADCASTS = 202;
+    //200->299 Events from Native
+    static final int STACK_EVENT = 200;
+    static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 201;
 
-    // commands for connection
-    static final int MESSAGE_PROCESS_RC_FEATURES = 301;
-    static final int MESSAGE_PROCESS_CONNECTION_CHANGE = 302;
-    static final int MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE = 303;
+    static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 203;
+    static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 204;
+    static final int MESSAGE_PROCESS_TRACK_CHANGED = 205;
+    static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 206;
+    static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 207;
+    static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 208;
+    static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 209;
+    static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE = 210;
+    static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 211;
+    static final int MESSAGE_PROCESS_FOLDER_PATH = 212;
+    static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 213;
+    static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 214;
+    static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 215;
+    static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216;
+    static final int MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS = 217;
+    static final int MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS = 218;
 
-    // Interal messages
-    static final int MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT = 401;
-    static final int MESSAGE_INTERNAL_MOVE_N_LEVELS_UP = 402;
-    static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 403;
+    //300->399 Events for Browsing
+    static final int MESSAGE_GET_FOLDER_ITEMS = 300;
+    static final int MESSAGE_PLAY_ITEM = 301;
+    static final int MSG_AVRCP_PASSTHRU = 302;
+    static final int MSG_AVRCP_SET_SHUFFLE = 303;
+    static final int MSG_AVRCP_SET_REPEAT = 304;
+
     static final int MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 404;
 
-    static final int ABS_VOL_TIMEOUT_MILLIS = 1000; //1s
-    static final int CMD_TIMEOUT_MILLIS = 5000; // 5s
-    // Fetch only 20 items at a time.
-    static final int GET_FOLDER_ITEMS_PAGINATION_SIZE = 20;
-    // Fetch no more than 1000 items per directory.
-    static final int MAX_FOLDER_ITEMS = 1000;
-
     /*
      * Base value for absolute volume from JNI
      */
@@ -107,531 +100,485 @@
     private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00;
     private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01;
 
-
-    private static final String TAG = "AvrcpControllerSM";
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
-
-    private final Context mContext;
     private final AudioManager mAudioManager;
+    private final boolean mIsVolumeFixed;
 
-    private final State mDisconnected;
-    private final State mConnected;
-    private final SetBrowsedPlayer mSetBrowsedPlayer;
-    private final SetAddresedPlayerAndPlayItem mSetAddrPlayer;
-    private final ChangeFolderPath mChangeFolderPath;
-    private final GetFolderList mGetFolderList;
-    private final GetPlayerListing mGetPlayerListing;
-    private final MoveToRoot mMoveToRoot;
+    protected final BluetoothDevice mDevice;
+    protected final byte[] mDeviceAddress;
+    protected final AvrcpControllerService mService;
+    protected final Disconnected mDisconnected;
+    protected final Connecting mConnecting;
+    protected final Connected mConnected;
+    protected final Disconnecting mDisconnecting;
 
-    private final Object mLock = new Object();
-    private static final ArrayList<MediaItem> EMPTY_MEDIA_ITEM_LIST = new ArrayList<>();
-    private static final MediaMetadata EMPTY_MEDIA_METADATA = new MediaMetadata.Builder().build();
+    protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED;
 
-    // APIs exist to access these so they must be thread safe
-    private Boolean mIsConnected = false;
-    private RemoteDevice mRemoteDevice;
-    private AvrcpPlayer mAddressedPlayer;
-
-    // Only accessed from State Machine processMessage
+    boolean mRemoteControlConnected = false;
+    boolean mBrowsingConnected = false;
+    final BrowseTree mBrowseTree;
+    private AvrcpPlayer mAddressedPlayer = new AvrcpPlayer();
+    private int mAddressedPlayerId = -1;
+    private SparseArray<AvrcpPlayer> mAvailablePlayerList = new SparseArray<AvrcpPlayer>();
     private int mVolumeChangedNotificationsToIgnore = 0;
-    private int mPreviousPercentageVol = -1;
+    private int mVolumeNotificationLabel = -1;
 
-    // Depth from root of current browsing. This can be used to move to root directly.
-    private int mBrowseDepth = 0;
+    GetFolderList mGetFolderList = null;
 
-    // Browse tree.
-    private BrowseTree mBrowseTree = new BrowseTree();
+    //Number of items to get in a single fetch
+    static final int ITEM_PAGE_SIZE = 20;
+    static final int CMD_TIMEOUT_MILLIS = 10000;
+    static final int ABS_VOL_TIMEOUT_MILLIS = 1000; //1s
 
-    AvrcpControllerStateMachine(Context context) {
+    AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service) {
         super(TAG);
-        mContext = context;
+        mDevice = device;
+        mDeviceAddress = Utils.getByteAddress(mDevice);
+        mService = service;
+        logD(device.toString());
 
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
-        mContext.registerReceiver(mBroadcastReceiver, filter);
-
+        mBrowseTree = new BrowseTree(mDevice);
         mDisconnected = new Disconnected();
+        mConnecting = new Connecting();
         mConnected = new Connected();
-
-        // Used to change folder path and fetch the new folder listing.
-        mSetBrowsedPlayer = new SetBrowsedPlayer();
-        mSetAddrPlayer = new SetAddresedPlayerAndPlayItem();
-        mChangeFolderPath = new ChangeFolderPath();
-        mGetFolderList = new GetFolderList();
-        mGetPlayerListing = new GetPlayerListing();
-        mMoveToRoot = new MoveToRoot();
+        mDisconnecting = new Disconnecting();
 
         addState(mDisconnected);
+        addState(mConnecting);
         addState(mConnected);
+        addState(mDisconnecting);
 
-        // Any action that needs blocking other requests to the state machine will be implemented as
-        // a separate substate of the mConnected state. Once transtition to the sub-state we should
-        // only handle the messages that are relevant to the sub-action. Everything else should be
-        // deferred so that once we transition to the mConnected we can process them hence.
-        addState(mSetBrowsedPlayer, mConnected);
-        addState(mSetAddrPlayer, mConnected);
-        addState(mChangeFolderPath, mConnected);
+        mGetFolderList = new GetFolderList();
         addState(mGetFolderList, mConnected);
-        addState(mGetPlayerListing, mConnected);
-        addState(mMoveToRoot, mConnected);
+        mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE);
+        mIsVolumeFixed = mAudioManager.isVolumeFixed();
 
         setInitialState(mDisconnected);
     }
 
-    class Disconnected extends State {
+    BrowseTree.BrowseNode findNode(String parentMediaId) {
+        logD("FindNode");
+        return mBrowseTree.findBrowseNodeByID(parentMediaId);
+    }
+
+    /**
+     * Get the current connection state
+     *
+     * @return current State
+     */
+    public int getState() {
+        return mMostRecentState;
+    }
+
+    /**
+     * Get the underlying device tracked by this state machine
+     *
+     * @return device in focus
+     */
+    public synchronized BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * send the connection event asynchronously
+     */
+    public boolean connect(StackEvent event) {
+        if (event.mBrowsingConnected) {
+            onBrowsingConnected();
+        }
+        mRemoteControlConnected = event.mRemoteControlConnected;
+        sendMessage(CONNECT);
+        return true;
+    }
+
+    /**
+     * send the Disconnect command asynchronously
+     */
+    public void disconnect() {
+        sendMessage(DISCONNECT);
+    }
+
+    /**
+     * Dump the current State Machine to the string builder.
+     *
+     * @param sb output string
+     */
+    public void dump(StringBuilder sb) {
+        ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "("
+                + mDevice.getName() + ") " + this.toString());
+    }
+
+    @Override
+    protected void unhandledMessage(Message msg) {
+        Log.w(TAG, "Unhandled message in state " + getCurrentState() + "msg.what=" + msg.what);
+    }
+
+    private static void logD(String message) {
+        if (DBG) {
+            Log.d(TAG, message);
+        }
+    }
+
+    synchronized void onBrowsingConnected() {
+        if (mBrowsingConnected) return;
+        mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode);
+        BluetoothMediaBrowserService.notifyChanged(mService
+                .sBrowseTree.mRootNode);
+        BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+        mBrowsingConnected = true;
+    }
+
+    synchronized void onBrowsingDisconnected() {
+        if (!mBrowsingConnected) return;
+        mAddressedPlayer.setPlayStatus(PlaybackStateCompat.STATE_ERROR);
+        mAddressedPlayer.updateCurrentTrack(null);
+        mBrowseTree.mNowPlayingNode.setCached(false);
+        BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
+        PlaybackStateCompat.Builder pbb = new PlaybackStateCompat.Builder();
+        pbb.setState(PlaybackStateCompat.STATE_ERROR, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN,
+                1.0f).setActions(0);
+        pbb.setErrorMessage(mService.getString(R.string.bluetooth_disconnected));
+        BluetoothMediaBrowserService.notifyChanged(pbb.build());
+        mService.sBrowseTree.mRootNode.removeChild(
+                mBrowseTree.mRootNode);
+        BluetoothMediaBrowserService.notifyChanged(mService
+                .sBrowseTree.mRootNode);
+        BluetoothMediaBrowserService.trackChanged(null);
+        mBrowsingConnected = false;
+    }
+
+    private void notifyChanged(BrowseTree.BrowseNode node) {
+        BluetoothMediaBrowserService.notifyChanged(node);
+    }
+
+    void requestContents(BrowseTree.BrowseNode node) {
+        sendMessage(MESSAGE_GET_FOLDER_ITEMS, node);
+
+        logD("Fetching " + node);
+    }
+
+    void nowPlayingContentChanged() {
+        mBrowseTree.mNowPlayingNode.setCached(false);
+        sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode);
+    }
+
+    protected class Disconnected extends State {
+        @Override
+        public void enter() {
+            logD("Enter Disconnected");
+            if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) {
+                sendMessage(CLEANUP);
+            }
+            broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED);
+        }
 
         @Override
-        public boolean processMessage(Message msg) {
-            if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
-            switch (msg.what) {
-                case MESSAGE_PROCESS_CONNECTION_CHANGE:
-                    if (msg.arg1 == BluetoothProfile.STATE_CONNECTED) {
-                        mBrowseTree.init();
-                        transitionTo(mConnected);
-                        BluetoothDevice rtDevice = (BluetoothDevice) msg.obj;
-                        synchronized (mLock) {
-                            mRemoteDevice = new RemoteDevice(rtDevice);
-                            mAddressedPlayer = new AvrcpPlayer();
-                            mIsConnected = true;
-                        }
-                        MetricsLogger.logProfileConnectionEvent(
-                                BluetoothMetricsProto.ProfileId.AVRCP_CONTROLLER);
-                        Intent intent = new Intent(
-                                BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
-                        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
-                                BluetoothProfile.STATE_DISCONNECTED);
-                        intent.putExtra(BluetoothProfile.EXTRA_STATE,
-                                BluetoothProfile.STATE_CONNECTED);
-                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
-                        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-                    }
+        public boolean processMessage(Message message) {
+            switch (message.what) {
+                case CONNECT:
+                    logD("Connect");
+                    transitionTo(mConnecting);
                     break;
-
-                default:
-                    Log.w(TAG,
-                            "Currently Disconnected not handling " + dumpMessageString(msg.what));
-                    return false;
+                case CLEANUP:
+                    mService.removeStateMachine(AvrcpControllerStateMachine.this);
+                    break;
             }
             return true;
         }
     }
 
+    protected class Connecting extends State {
+        @Override
+        public void enter() {
+            logD("Enter Connecting");
+            broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTING);
+            transitionTo(mConnected);
+        }
+    }
+
+
     class Connected extends State {
-        @Override
-        public boolean processMessage(Message msg) {
-            if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
-            A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
-            synchronized (mLock) {
-                switch (msg.what) {
-                    case MESSAGE_SEND_PASS_THROUGH_CMD:
-                        BluetoothDevice device = (BluetoothDevice) msg.obj;
-                        AvrcpControllerService.sendPassThroughCommandNative(
-                                Utils.getByteAddress(device), msg.arg1, msg.arg2);
-                        if (a2dpSinkService != null) {
-                            if (DBG) Log.d(TAG, " inform AVRCP Commands to A2DP Sink ");
-                            a2dpSinkService.informAvrcpPassThroughCmd(device, msg.arg1, msg.arg2);
-                        }
-                        break;
-
-                    case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
-                        AvrcpControllerService.sendGroupNavigationCommandNative(
-                                mRemoteDevice.getBluetoothAddress(), msg.arg1, msg.arg2);
-                        break;
-
-                    case MESSAGE_GET_NOW_PLAYING_LIST:
-                        mGetFolderList.setFolder((String) msg.obj);
-                        mGetFolderList.setBounds((int) msg.arg1, (int) msg.arg2);
-                        mGetFolderList.setScope(AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING);
-                        transitionTo(mGetFolderList);
-                        break;
-
-                    case MESSAGE_GET_FOLDER_LIST:
-                        // Whenever we transition we set the information for folder we need to
-                        // return result.
-                        mGetFolderList.setBounds(msg.arg1, msg.arg2);
-                        mGetFolderList.setFolder((String) msg.obj);
-                        mGetFolderList.setScope(AvrcpControllerService.BROWSE_SCOPE_VFS);
-                        transitionTo(mGetFolderList);
-                        break;
-
-                    case MESSAGE_GET_PLAYER_LIST:
-                        AvrcpControllerService.getPlayerListNative(
-                                mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
-                                (byte) msg.arg2);
-                        transitionTo(mGetPlayerListing);
-                        sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
-                        break;
-
-                    case MESSAGE_CHANGE_FOLDER_PATH: {
-                        int direction = msg.arg1;
-                        Bundle b = (Bundle) msg.obj;
-                        String uid = b.getString(AvrcpControllerService.EXTRA_FOLDER_BT_ID);
-                        String fid = b.getString(AvrcpControllerService.EXTRA_FOLDER_ID);
-
-                        // String is encoded as a Hex String (mostly for display purposes)
-                        // hence convert this back to real byte string.
-                        AvrcpControllerService.changeFolderPathNative(
-                                mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
-                                AvrcpControllerService.hexStringToByteUID(uid));
-                        mChangeFolderPath.setFolder(fid);
-                        transitionTo(mChangeFolderPath);
-                        sendMessage(MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT, (byte) msg.arg1);
-                        sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
-                        break;
-                    }
-
-                    case MESSAGE_FETCH_ATTR_AND_PLAY_ITEM: {
-                        int scope = msg.arg1;
-                        String playItemUid = (String) msg.obj;
-                        BrowseTree.BrowseNode currBrPlayer = mBrowseTree.getCurrentBrowsedPlayer();
-                        BrowseTree.BrowseNode currAddrPlayer =
-                                mBrowseTree.getCurrentAddressedPlayer();
-                        if (DBG) {
-                            Log.d(TAG, "currBrPlayer " + currBrPlayer + " currAddrPlayer "
-                                    + currAddrPlayer);
-                        }
-
-                        if (currBrPlayer == null || currBrPlayer.equals(currAddrPlayer)) {
-                            // String is encoded as a Hex String (mostly for display purposes)
-                            // hence convert this back to real byte string.
-                            // NOTE: It may be possible that sending play while the same item is
-                            // playing leads to reset of track.
-                            AvrcpControllerService.playItemNative(
-                                    mRemoteDevice.getBluetoothAddress(), (byte) scope,
-                                    AvrcpControllerService.hexStringToByteUID(playItemUid),
-                                    (int) 0);
-                        } else {
-                            // Send out the request for setting addressed player.
-                            AvrcpControllerService.setAddressedPlayerNative(
-                                    mRemoteDevice.getBluetoothAddress(),
-                                    currBrPlayer.getPlayerID());
-                            mSetAddrPlayer.setItemAndScope(currBrPlayer.getID(), playItemUid,
-                                    scope);
-                            transitionTo(mSetAddrPlayer);
-                        }
-                        break;
-                    }
-
-                    case MESSAGE_SET_BROWSED_PLAYER: {
-                        AvrcpControllerService.setBrowsedPlayerNative(
-                                mRemoteDevice.getBluetoothAddress(), (int) msg.arg1);
-                        mSetBrowsedPlayer.setFolder((String) msg.obj);
-                        transitionTo(mSetBrowsedPlayer);
-                        break;
-                    }
-
-                    case MESSAGE_PROCESS_SET_ADDRESSED_PLAYER:
-                        AvrcpControllerService.getPlayerListNative(
-                                mRemoteDevice.getBluetoothAddress(), 0, 255);
-                        transitionTo(mGetPlayerListing);
-                        sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
-                        break;
-
-
-                    case MESSAGE_PROCESS_CONNECTION_CHANGE:
-                        if (msg.arg1 == BluetoothProfile.STATE_DISCONNECTED) {
-                            synchronized (mLock) {
-                                mIsConnected = false;
-                                mRemoteDevice = null;
-                            }
-                            mBrowseTree.clear();
-                            transitionTo(mDisconnected);
-                            BluetoothDevice rtDevice = (BluetoothDevice) msg.obj;
-                            Intent intent = new Intent(
-                                    BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
-                            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
-                                    BluetoothProfile.STATE_CONNECTED);
-                            intent.putExtra(BluetoothProfile.EXTRA_STATE,
-                                    BluetoothProfile.STATE_DISCONNECTED);
-                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
-                            mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-                        }
-                        break;
-
-                    case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
-                        // Service tells us if the browse is connected or disconnected.
-                        // This is useful only for deciding whether to send browse commands rest of
-                        // the connection state handling should be done via the message
-                        // MESSAGE_PROCESS_CONNECTION_CHANGE.
-                        Intent intent = new Intent(
-                                AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
-                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, (BluetoothDevice) msg.obj);
-                        if (DBG) {
-                            Log.d(TAG, "Browse connection state " + msg.arg1);
-                        }
-                        if (msg.arg1 == 1) {
-                            intent.putExtra(BluetoothProfile.EXTRA_STATE,
-                                    BluetoothProfile.STATE_CONNECTED);
-                        } 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;
-                        } else {
-                            Log.w(TAG, "Incorrect browse state " + msg.arg1);
-                        }
-
-                        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-                        break;
-
-                    case MESSAGE_PROCESS_RC_FEATURES:
-                        mRemoteDevice.setRemoteFeatures(msg.arg1);
-                        break;
-
-                    case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                        mVolumeChangedNotificationsToIgnore++;
-                        removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
-                        sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
-                                ABS_VOL_TIMEOUT_MILLIS);
-                        setAbsVolume(msg.arg1, msg.arg2);
-                        break;
-
-                    case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: {
-                        mRemoteDevice.setNotificationLabel(msg.arg1);
-                        mRemoteDevice.setAbsVolNotificationRequested(true);
-                        int percentageVol = getVolumePercentage();
-                        if (DBG) {
-                            Log.d(TAG, " Sending Interim Response = " + percentageVol + " label "
-                                    + msg.arg1);
-                        }
-                        AvrcpControllerService.sendRegisterAbsVolRspNative(
-                                mRemoteDevice.getBluetoothAddress(), NOTIFICATION_RSP_TYPE_INTERIM,
-                                percentageVol, mRemoteDevice.getNotificationLabel());
-                    }
-                    break;
-
-                    case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: {
-                        if (mVolumeChangedNotificationsToIgnore > 0) {
-                            mVolumeChangedNotificationsToIgnore--;
-                            if (mVolumeChangedNotificationsToIgnore == 0) {
-                                removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
-                            }
-                        } else {
-                            if (mRemoteDevice.getAbsVolNotificationRequested()) {
-                                int percentageVol = getVolumePercentage();
-                                if (percentageVol != mPreviousPercentageVol) {
-                                    AvrcpControllerService.sendRegisterAbsVolRspNative(
-                                            mRemoteDevice.getBluetoothAddress(),
-                                            NOTIFICATION_RSP_TYPE_CHANGED, percentageVol,
-                                            mRemoteDevice.getNotificationLabel());
-                                    mPreviousPercentageVol = percentageVol;
-                                    mRemoteDevice.setAbsVolNotificationRequested(false);
-                                }
-                            }
-                        }
-                    }
-                    break;
-
-                    case MESSAGE_INTERNAL_ABS_VOL_TIMEOUT:
-                        // Volume changed notifications should come back promptly from the
-                        // AudioManager, if for some reason some notifications were squashed don't
-                        // prevent future notifications.
-                        if (DBG) Log.d(TAG, "Timed out on volume changed notification");
-                        mVolumeChangedNotificationsToIgnore = 0;
-                        break;
-
-                    case MESSAGE_PROCESS_TRACK_CHANGED:
-                        // Music start playing automatically and update Metadata
-                        mAddressedPlayer.updateCurrentTrack((TrackInfo) msg.obj);
-                        broadcastMetaDataChanged(
-                                mAddressedPlayer.getCurrentTrack().getMediaMetaData());
-                        break;
-
-                    case MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                        if (msg.arg2 != -1) {
-                            mAddressedPlayer.setPlayTime(msg.arg2);
-                            broadcastPlayBackStateChanged(getCurrentPlayBackState());
-                        }
-                        break;
-
-                    case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                        int status = msg.arg1;
-                        mAddressedPlayer.setPlayStatus(status);
-                        broadcastPlayBackStateChanged(getCurrentPlayBackState());
-                        if (status == PlaybackState.STATE_PLAYING) {
-                            a2dpSinkService.informTGStatePlaying(mRemoteDevice.mBTDevice, true);
-                        } else if (status == PlaybackState.STATE_PAUSED
-                                || status == PlaybackState.STATE_STOPPED) {
-                            a2dpSinkService.informTGStatePlaying(mRemoteDevice.mBTDevice, false);
-                        }
-                        break;
-
-                    default:
-                        return false;
-                }
-            }
-            return true;
-        }
-    }
-
-    // Handle the change folder path meta-action.
-    // a) Send Change folder command
-    // b) Once successful transition to folder fetch state.
-    class ChangeFolderPath extends CmdState {
-        private static final String STATE_TAG = "AVRCPSM.ChangeFolderPath";
-        private int mTmpIncrDirection;
-        private String mID = "";
-
-        public void setFolder(String id) {
-            mID = id;
-        }
+        private static final String STATE_TAG = "Avrcp.ConnectedAvrcpController";
+        private int mCurrentlyHeldKey = 0;
 
         @Override
         public void enter() {
+            if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) {
+                broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
+                BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
+            } else {
+                logD("ReEnteringConnected");
+            }
             super.enter();
-            mTmpIncrDirection = -1;
         }
 
         @Override
         public boolean processMessage(Message msg) {
-            if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
+            logD(STATE_TAG + " processMessage " + msg.what);
             switch (msg.what) {
-                case MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT:
-                    mTmpIncrDirection = msg.arg1;
-                    break;
-
-                case MESSAGE_PROCESS_FOLDER_PATH: {
-                    // Fetch the listing of objects in this folder.
-                    if (DBG) {
-                        Log.d(STATE_TAG,
-                                "MESSAGE_PROCESS_FOLDER_PATH returned " + msg.arg1 + " elements");
-                    }
-
-                    // Update the folder depth.
-                    if (mTmpIncrDirection
-                            == AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP) {
-                        mBrowseDepth -= 1;
-                    } else if (mTmpIncrDirection
-                            == AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN) {
-                        mBrowseDepth += 1;
-                    } else {
-                        throw new IllegalStateException("incorrect nav " + mTmpIncrDirection);
-                    }
-                    if (DBG) Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
-
-                    if (msg.arg1 > 0) {
-                        sendMessage(MESSAGE_GET_FOLDER_LIST, 0, msg.arg1 - 1, mID);
-                    } else {
-                        // Return an empty response to the upper layer.
-                        broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST);
-                    }
-                    mBrowseTree.setCurrentBrowsedFolder(mID);
-                    transitionTo(mConnected);
-                    break;
-                }
-
-                case MESSAGE_INTERNAL_CMD_TIMEOUT:
-                    // We timed out changing folders. It is imperative we tell
-                    // the upper layers that we failed by giving them an empty list.
-                    Log.e(STATE_TAG, "change folder failed, sending empty list.");
-                    broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST);
-                    transitionTo(mConnected);
-                    break;
-
-                case MESSAGE_SEND_PASS_THROUGH_CMD:
-                case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
                 case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
+                    mVolumeChangedNotificationsToIgnore++;
+                    removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
+                    sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
+                            ABS_VOL_TIMEOUT_MILLIS);
+                    handleAbsVolumeRequest(msg.arg1, msg.arg2);
+                    return true;
+
                 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
+                    mVolumeNotificationLabel = msg.arg1;
+                    mService.sendRegisterAbsVolRspNative(mDeviceAddress,
+                            NOTIFICATION_RSP_TYPE_INTERIM,
+                            getAbsVolume(), mVolumeNotificationLabel);
+                    return true;
+
+                case MESSAGE_GET_FOLDER_ITEMS:
+                    transitionTo(mGetFolderList);
+                    return true;
+
+                case MESSAGE_PLAY_ITEM:
+                    //Set Addressed Player
+                    playItem((BrowseTree.BrowseNode) msg.obj);
+                    return true;
+
+                case MSG_AVRCP_PASSTHRU:
+                    passThru(msg.arg1);
+                    return true;
+
+                case MSG_AVRCP_SET_REPEAT:
+                    setRepeat(msg.arg1);
+                    return true;
+
+                case MSG_AVRCP_SET_SHUFFLE:
+                    setShuffle(msg.arg1);
+                    return true;
+
                 case MESSAGE_PROCESS_TRACK_CHANGED:
-                case MESSAGE_PROCESS_PLAY_POS_CHANGED:
+                    mAddressedPlayer.updateCurrentTrack((MediaMetadata) msg.obj);
+                    BluetoothMediaBrowserService.trackChanged((MediaMetadata) msg.obj);
+                    return true;
+
                 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
-                case MESSAGE_STOP_METADATA_BROADCASTS:
-                case MESSAGE_START_METADATA_BROADCASTS:
-                case MESSAGE_PROCESS_CONNECTION_CHANGE:
-                case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
-                    // All of these messages should be handled by parent state immediately.
-                    return false;
+                    mAddressedPlayer.setPlayStatus(msg.arg1);
+                    BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+                    if (mAddressedPlayer.getPlaybackState().getState()
+                            == PlaybackStateCompat.STATE_PLAYING
+                            && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE
+                            && !shouldRequestFocus()) {
+                        sendMessage(MSG_AVRCP_PASSTHRU,
+                                AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
+                    }
+                    return true;
+
+                case MESSAGE_PROCESS_PLAY_POS_CHANGED:
+                    if (msg.arg2 != -1) {
+                        mAddressedPlayer.setPlayTime(msg.arg2);
+
+                        BluetoothMediaBrowserService.notifyChanged(
+                                mAddressedPlayer.getPlaybackState());
+                    }
+                    return true;
+
+                case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED:
+                    mAddressedPlayerId = msg.arg1;
+                    logD("AddressedPlayer = " + mAddressedPlayerId);
+                    AvrcpPlayer updatedPlayer = mAvailablePlayerList.get(mAddressedPlayerId);
+                    if (updatedPlayer != null) {
+                        mAddressedPlayer = updatedPlayer;
+                        logD("AddressedPlayer = " + mAddressedPlayer.getName());
+                    } else {
+                        mBrowseTree.mRootNode.setCached(false);
+                        mBrowseTree.mRootNode.setExpectedChildren(255);
+                        BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode);
+                    }
+                    return true;
+
+                case MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS:
+                    mAddressedPlayer.setSupportedPlayerApplicationSettings(
+                            (PlayerApplicationSettings) msg.obj);
+                    BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+                    return true;
+
+                case MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS:
+                    mAddressedPlayer.setCurrentPlayerApplicationSettings(
+                            (PlayerApplicationSettings) msg.obj);
+                    BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+                    return true;
+
+                case DISCONNECT:
+                    transitionTo(mDisconnecting);
+                    return true;
 
                 default:
-                    if (DBG) {
-                        Log.d(STATE_TAG, "deferring message " + msg.what + " to Connected state.");
-                    }
-                    deferMessage(msg);
+                    return super.processMessage(msg);
             }
-            return true;
+
+        }
+
+        private void playItem(BrowseTree.BrowseNode node) {
+            if (node == null) {
+                Log.w(TAG, "Invalid item to play");
+            } else {
+                mService.playItemNative(
+                        mDeviceAddress, node.getScope(),
+                        node.getBluetoothID(), 0);
+            }
+        }
+
+        private synchronized void passThru(int cmd) {
+            logD("msgPassThru " + cmd);
+            // Some keys should be held until the next event.
+            if (mCurrentlyHeldKey != 0) {
+                mService.sendPassThroughCommandNative(
+                        mDeviceAddress, mCurrentlyHeldKey,
+                        AvrcpControllerService.KEY_STATE_RELEASED);
+
+                if (mCurrentlyHeldKey == cmd) {
+                    // Return to prevent starting FF/FR operation again
+                    mCurrentlyHeldKey = 0;
+                    return;
+                } else {
+                    // FF/FR is in progress and other operation is desired
+                    // so after stopping FF/FR, not returning so that command
+                    // can be sent for the desired operation.
+                    mCurrentlyHeldKey = 0;
+                }
+            }
+
+            // Send the pass through.
+            mService.sendPassThroughCommandNative(mDeviceAddress, cmd,
+                    AvrcpControllerService.KEY_STATE_PRESSED);
+
+            if (isHoldableKey(cmd)) {
+                // Release cmd next time a command is sent.
+                mCurrentlyHeldKey = cmd;
+            } else {
+                mService.sendPassThroughCommandNative(mDeviceAddress,
+                        cmd, AvrcpControllerService.KEY_STATE_RELEASED);
+            }
+        }
+
+        private boolean isHoldableKey(int cmd) {
+            return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND)
+                    || (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF);
+        }
+
+        private void setRepeat(int repeatMode) {
+            mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1,
+                    new byte[]{PlayerApplicationSettings.REPEAT_STATUS}, new byte[]{
+                            PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal(
+                                    PlayerApplicationSettings.REPEAT_STATUS, repeatMode)});
+        }
+
+        private void setShuffle(int shuffleMode) {
+            mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1,
+                    new byte[]{PlayerApplicationSettings.SHUFFLE_STATUS}, new byte[]{
+                            PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal(
+                                    PlayerApplicationSettings.SHUFFLE_STATUS, shuffleMode)});
         }
     }
 
     // Handle the get folder listing action
     // a) Fetch the listing of folders
     // b) Once completed return the object listing
-    class GetFolderList extends CmdState {
-        private static final String STATE_TAG = "AVRCPSM.GetFolderList";
+    class GetFolderList extends State {
+        private static final String STATE_TAG = "Avrcp.GetFolderList";
 
-        String mID = "";
-        int mStartInd;
-        int mEndInd;
-        int mCurrInd;
-        int mScope;
-        private ArrayList<MediaItem> mFolderList = new ArrayList<>();
+        boolean mAbort;
+        BrowseTree.BrowseNode mBrowseNode;
+        BrowseTree.BrowseNode mNextStep;
 
         @Override
         public void enter() {
+            logD(STATE_TAG + " Entering GetFolderList");
             // Setup the timeouts.
+            sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
             super.enter();
-            mCurrInd = 0;
-            mFolderList.clear();
-            callNativeFunctionForScope(mStartInd,
-                    Math.min(mEndInd, mStartInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1));
-        }
-
-        public void setScope(int scope) {
-            mScope = scope;
-        }
-
-        public void setFolder(String id) {
-            if (DBG) Log.d(STATE_TAG, "Setting folder to " + id);
-            mID = id;
-        }
-
-        public void setBounds(int startInd, int endInd) {
-            if (DBG) {
-                Log.d(STATE_TAG, "startInd " + startInd + " endInd " + endInd);
+            mAbort = false;
+            Message msg = getCurrentMessage();
+            if (msg.what == MESSAGE_GET_FOLDER_ITEMS) {
+                {
+                    logD(STATE_TAG + " new Get Request");
+                    mBrowseNode = (BrowseTree.BrowseNode) msg.obj;
+                }
             }
-            mStartInd = startInd;
-            mEndInd = Math.min(endInd, MAX_FOLDER_ITEMS);
+
+            if (mBrowseNode == null) {
+                transitionTo(mConnected);
+            } else {
+                navigateToFolderOrRetrieve(mBrowseNode);
+            }
         }
 
         @Override
         public boolean processMessage(Message msg) {
-            Log.d(STATE_TAG, "processMessage " + msg.what);
+            logD(STATE_TAG + " processMessage " + msg.what);
             switch (msg.what) {
                 case MESSAGE_PROCESS_GET_FOLDER_ITEMS:
                     ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj;
-                    mFolderList.addAll(folderList);
-                    if (DBG) {
-                        Log.d(STATE_TAG,
-                                "Start " + mStartInd + " End " + mEndInd + " Curr " + mCurrInd
-                                        + " received " + folderList.size());
-                    }
-                    mCurrInd += folderList.size();
+                    int endIndicator = mBrowseNode.getExpectedChildren() - 1;
+                    logD("GetFolderItems: End " + endIndicator
+                            + " received " + folderList.size());
 
                     // Always update the node so that the user does not wait forever
                     // for the list to populate.
-                    sendFolderBroadcastAndUpdateNode();
+                    mBrowseNode.addChildren(folderList);
+                    notifyChanged(mBrowseNode);
 
-                    if (mCurrInd > mEndInd || folderList.size() == 0) {
+                    if (mBrowseNode.getChildrenCount() >= endIndicator || folderList.size() == 0
+                            || mAbort) {
                         // If we have fetched all the elements or if the remotes sends us 0 elements
                         // (which can lead us into a loop since mCurrInd does not proceed) we simply
                         // abort.
+                        mBrowseNode.setCached(true);
                         transitionTo(mConnected);
                     } else {
                         // Fetch the next set of items.
-                        callNativeFunctionForScope(mCurrInd, Math.min(mEndInd,
-                                mCurrInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1));
+                        fetchContents(mBrowseNode);
                         // Reset the timeout message since we are doing a new fetch now.
                         removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
                         sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
                     }
                     break;
+                case MESSAGE_PROCESS_SET_BROWSED_PLAYER:
+                    mBrowseTree.setCurrentBrowsedPlayer(mNextStep.getID(), msg.arg1, msg.arg2);
+                    removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
+                    sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+                    navigateToFolderOrRetrieve(mBrowseNode);
+                    break;
+
+                case MESSAGE_PROCESS_FOLDER_PATH:
+                    mBrowseTree.setCurrentBrowsedFolder(mNextStep.getID());
+                    mBrowseTree.getCurrentBrowsedFolder().setExpectedChildren(msg.arg1);
+
+                    if (mAbort) {
+                        transitionTo(mConnected);
+                    } else {
+                        removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
+                        sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+                        navigateToFolderOrRetrieve(mBrowseNode);
+                    }
+                    break;
+
+                case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
+                    BrowseTree.BrowseNode rootNode = mBrowseTree.mRootNode;
+                    if (!rootNode.isCached()) {
+                        List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj;
+                        mAvailablePlayerList.clear();
+                        for (AvrcpPlayer player : playerList) {
+                            mAvailablePlayerList.put(player.getId(), player);
+                        }
+                        rootNode.addChildren(playerList);
+                        mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT);
+                        rootNode.setExpectedChildren(playerList.size());
+                        rootNode.setCached(true);
+                        notifyChanged(rootNode);
+                    }
+                    transitionTo(mConnected);
+                    break;
 
                 case MESSAGE_INTERNAL_CMD_TIMEOUT:
                     // We have timed out to execute the request, we should simply send
                     // whatever listing we have gotten until now.
-                    sendFolderBroadcastAndUpdateNode();
+                    Log.w(TAG, "TIMEOUT");
                     transitionTo(mConnected);
                     break;
 
@@ -639,651 +586,302 @@
                     // If we have gotten an error for OUT OF RANGE we have
                     // already sent all the items to the client hence simply
                     // transition to Connected state here.
+                    mBrowseNode.setCached(true);
                     transitionTo(mConnected);
                     break;
 
-                case MESSAGE_CHANGE_FOLDER_PATH:
-                case MESSAGE_FETCH_ATTR_AND_PLAY_ITEM:
-                case MESSAGE_GET_PLAYER_LIST:
-                case MESSAGE_GET_NOW_PLAYING_LIST:
-                case MESSAGE_SET_BROWSED_PLAYER:
-                    // A new request has come in, no need to fetch more.
-                    mEndInd = 0;
-                    deferMessage(msg);
+                case MESSAGE_GET_FOLDER_ITEMS:
+                    if (!mBrowseNode.equals(msg.obj)) {
+                        if (shouldAbort(mBrowseNode.getScope(),
+                                ((BrowseTree.BrowseNode) msg.obj).getScope())) {
+                            mAbort = true;
+                        }
+                        deferMessage(msg);
+                        logD("GetFolderItems: Go Get Another Directory");
+                    } else {
+                        logD("GetFolderItems: Get The Same Directory, ignore");
+                    }
                     break;
 
-                case MESSAGE_SEND_PASS_THROUGH_CMD:
-                case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
-                case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
-                case MESSAGE_PROCESS_TRACK_CHANGED:
-                case MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
-                case MESSAGE_STOP_METADATA_BROADCASTS:
-                case MESSAGE_START_METADATA_BROADCASTS:
-                case MESSAGE_PROCESS_CONNECTION_CHANGE:
-                case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
+                default:
                     // All of these messages should be handled by parent state immediately.
                     return false;
-
-                default:
-                    if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
-                    deferMessage(msg);
             }
             return true;
         }
 
-        private void sendFolderBroadcastAndUpdateNode() {
-            BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID);
-            if (bn == null) {
-                Log.e(TAG, "Can not find BrowseNode by ID: " + mID);
-                return;
+        /**
+         * shouldAbort calculates the cases where fetching the current directory is no longer
+         * necessary.
+         *
+         * @return true:  a new folder in the same scope
+         * a new player while fetching contents of a folder
+         * false: other cases, specifically Now Playing while fetching a folder
+         */
+        private boolean shouldAbort(int currentScope, int fetchScope) {
+            if ((currentScope == fetchScope)
+                    || (currentScope == AvrcpControllerService.BROWSE_SCOPE_VFS
+                    && fetchScope == AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST)) {
+                return true;
             }
-            if (bn.isPlayer()) {
-                // Add the now playing folder.
-                MediaDescription.Builder mdb = new MediaDescription.Builder();
-                mdb.setMediaId(BrowseTree.NOW_PLAYING_PREFIX + ":" + bn.getPlayerID());
-                mdb.setTitle(BrowseTree.NOW_PLAYING_PREFIX);
-                Bundle mdBundle = new Bundle();
-                mdBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY,
-                        BrowseTree.NOW_PLAYING_PREFIX + ":" + bn.getID());
-                mdb.setExtras(mdBundle);
-                mFolderList.add(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE));
-            }
-            mBrowseTree.refreshChildren(bn, mFolderList);
-            broadcastFolderList(mID, mFolderList);
-
-            // For now playing we need to set the current browsed folder here.
-            // For normal folders it is set after ChangeFolderPath.
-            if (mScope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
-                mBrowseTree.setCurrentBrowsedFolder(mID);
-            }
+            return false;
         }
 
-        private void callNativeFunctionForScope(int start, int end) {
-            switch (mScope) {
+        private void fetchContents(BrowseTree.BrowseNode target) {
+            int start = target.getChildrenCount();
+            int end = Math.min(target.getExpectedChildren(), target.getChildrenCount()
+                    + ITEM_PAGE_SIZE) - 1;
+            switch (target.getScope()) {
+                case AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST:
+                    mService.getPlayerListNative(mDeviceAddress,
+                            start, end);
+                    break;
                 case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING:
-                    AvrcpControllerService.getNowPlayingListNative(
-                            mRemoteDevice.getBluetoothAddress(), start, end);
+                    mService.getNowPlayingListNative(
+                            mDeviceAddress, start, end);
                     break;
                 case AvrcpControllerService.BROWSE_SCOPE_VFS:
-                    AvrcpControllerService.getFolderListNative(mRemoteDevice.getBluetoothAddress(),
+                    mService.getFolderListNative(mDeviceAddress,
                             start, end);
                     break;
                 default:
-                    Log.e(STATE_TAG, "Scope " + mScope + " cannot be handled here.");
+                    Log.e(TAG, STATE_TAG + " Scope " + target.getScope()
+                            + " cannot be handled here.");
             }
         }
-    }
 
-    // Handle the get player listing action
-    // a) Fetch the listing of players
-    // b) Once completed return the object listing
-    class GetPlayerListing extends CmdState {
-        private static final String STATE_TAG = "AVRCPSM.GetPlayerList";
-
-        @Override
-        public boolean processMessage(Message msg) {
-            if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
-            switch (msg.what) {
-                case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
-                    List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj;
-                    mBrowseTree.refreshChildren(BrowseTree.ROOT, playerList);
-                    ArrayList<MediaItem> mediaItemList = new ArrayList<>();
-                    for (BrowseTree.BrowseNode c : mBrowseTree.findBrowseNodeByID(BrowseTree.ROOT)
-                            .getChildren()) {
-                        mediaItemList.add(c.getMediaItem());
-                    }
-                    broadcastFolderList(BrowseTree.ROOT, mediaItemList);
-                    mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT);
+        /* One of several things can happen when trying to get a folder list
+         *
+         *
+         * 0: The folder handle is no longer valid
+         * 1: The folder contents can be retrieved directly (NowPlaying, Root, Current)
+         * 2: The folder is a browsable player
+         * 3: The folder is a non browsable player
+         * 4: The folder is not a child of the current folder
+         * 5: The folder is a child of the current folder
+         *
+         */
+        private void navigateToFolderOrRetrieve(BrowseTree.BrowseNode target) {
+            mNextStep = mBrowseTree.getNextStepToFolder(target);
+            logD("NAVIGATING From "
+                    + mBrowseTree.getCurrentBrowsedFolder().toString());
+            logD("NAVIGATING Toward " + target.toString());
+            if (mNextStep == null) {
+                return;
+            } else if (target.equals(mBrowseTree.mNowPlayingNode)
+                    || target.equals(mBrowseTree.mRootNode)
+                    || mNextStep.equals(mBrowseTree.getCurrentBrowsedFolder())) {
+                fetchContents(mNextStep);
+            } else if (mNextStep.isPlayer()) {
+                logD("NAVIGATING Player " + mNextStep.toString());
+                if (mNextStep.isBrowsable()) {
+                    mService.setBrowsedPlayerNative(
+                            mDeviceAddress, (int) mNextStep.getBluetoothID());
+                } else {
+                    logD("Player doesn't support browsing");
+                    mNextStep.setCached(true);
                     transitionTo(mConnected);
-                    break;
+                }
+            } else if (mNextStep.equals(mBrowseTree.mNavigateUpNode)) {
+                logD("NAVIGATING UP " + mNextStep.toString());
+                mNextStep = mBrowseTree.getCurrentBrowsedFolder().getParent();
+                mBrowseTree.getCurrentBrowsedFolder().setCached(false);
 
-                case MESSAGE_INTERNAL_CMD_TIMEOUT:
-                    // We have timed out to execute the request.
-                    // Send an empty list here.
-                    broadcastFolderList(BrowseTree.ROOT, EMPTY_MEDIA_ITEM_LIST);
-                    transitionTo(mConnected);
-                    break;
+                mService.changeFolderPathNative(
+                        mDeviceAddress,
+                        AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
+                        0);
 
-                case MESSAGE_SEND_PASS_THROUGH_CMD:
-                case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
-                case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
-                case MESSAGE_PROCESS_TRACK_CHANGED:
-                case MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
-                case MESSAGE_STOP_METADATA_BROADCASTS:
-                case MESSAGE_START_METADATA_BROADCASTS:
-                case MESSAGE_PROCESS_CONNECTION_CHANGE:
-                case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
-                    // All of these messages should be handled by parent state immediately.
-                    return false;
-
-                default:
-                    if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
-                    deferMessage(msg);
+            } else {
+                logD("NAVIGATING DOWN " + mNextStep.toString());
+                mService.changeFolderPathNative(
+                        mDeviceAddress,
+                        AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN,
+                        mNextStep.getBluetoothID());
             }
-            return true;
-        }
-    }
-
-    class MoveToRoot extends CmdState {
-        private static final String STATE_TAG = "AVRCPSM.MoveToRoot";
-        private String mID = "";
-
-        public void setFolder(String id) {
-            if (DBG) Log.d(STATE_TAG, "setFolder " + id);
-            mID = id;
-        }
-
-        @Override
-        public void enter() {
-            // Setup the timeouts.
-            super.enter();
-
-            // We need to move mBrowseDepth levels up. The following message is
-            // completely internal to this state.
-            sendMessage(MESSAGE_INTERNAL_MOVE_N_LEVELS_UP);
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            if (DBG) {
-                Log.d(STATE_TAG, "processMessage " + msg.what + " browse depth " + mBrowseDepth);
-            }
-            switch (msg.what) {
-                case MESSAGE_INTERNAL_MOVE_N_LEVELS_UP:
-                    if (mBrowseDepth == 0) {
-                        Log.w(STATE_TAG, "Already in root!");
-                        transitionTo(mConnected);
-                        sendMessage(MESSAGE_GET_FOLDER_LIST, 0, 0xff, mID);
-                    } else {
-                        AvrcpControllerService.changeFolderPathNative(
-                                mRemoteDevice.getBluetoothAddress(),
-                                (byte) AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
-                                AvrcpControllerService.hexStringToByteUID(null));
-                    }
-                    break;
-
-                case MESSAGE_PROCESS_FOLDER_PATH:
-                    mBrowseDepth -= 1;
-                    if (DBG) Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
-                    if (mBrowseDepth < 0) {
-                        throw new IllegalArgumentException("Browse depth negative!");
-                    }
-
-                    sendMessage(MESSAGE_INTERNAL_MOVE_N_LEVELS_UP);
-                    break;
-
-                case MESSAGE_INTERNAL_CMD_TIMEOUT:
-                    broadcastFolderList(BrowseTree.ROOT, EMPTY_MEDIA_ITEM_LIST);
-                    transitionTo(mConnected);
-                    break;
-
-                case MESSAGE_SEND_PASS_THROUGH_CMD:
-                case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
-                case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
-                case MESSAGE_PROCESS_TRACK_CHANGED:
-                case MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
-                case MESSAGE_STOP_METADATA_BROADCASTS:
-                case MESSAGE_START_METADATA_BROADCASTS:
-                case MESSAGE_PROCESS_CONNECTION_CHANGE:
-                case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
-                    // All of these messages should be handled by parent state immediately.
-                    return false;
-
-                default:
-                    if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
-                    deferMessage(msg);
-            }
-            return true;
-        }
-    }
-
-    class SetBrowsedPlayer extends CmdState {
-        private static final String STATE_TAG = "AVRCPSM.SetBrowsedPlayer";
-        String mID = "";
-
-        public void setFolder(String id) {
-            mID = id;
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
-            switch (msg.what) {
-                case MESSAGE_PROCESS_SET_BROWSED_PLAYER:
-                    // Set the new depth.
-                    if (DBG) Log.d(STATE_TAG, "player depth " + msg.arg2);
-                    mBrowseDepth = msg.arg2;
-
-                    // If we already on top of player and there is no content.
-                    // This should very rarely happen.
-                    if (mBrowseDepth == 0 && msg.arg1 == 0) {
-                        broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST);
-                        transitionTo(mConnected);
-                    } else {
-                        // Otherwise move to root and fetch the listing.
-                        // the MoveToRoot#enter() function takes care of fetch.
-                        mMoveToRoot.setFolder(mID);
-                        transitionTo(mMoveToRoot);
-                    }
-                    mBrowseTree.setCurrentBrowsedFolder(mID);
-                    // Also set the browsed player here.
-                    mBrowseTree.setCurrentBrowsedPlayer(mID);
-                    break;
-
-                case MESSAGE_INTERNAL_CMD_TIMEOUT:
-                    broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST);
-                    transitionTo(mConnected);
-                    break;
-
-                case MESSAGE_SEND_PASS_THROUGH_CMD:
-                case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
-                case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
-                case MESSAGE_PROCESS_TRACK_CHANGED:
-                case MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
-                case MESSAGE_STOP_METADATA_BROADCASTS:
-                case MESSAGE_START_METADATA_BROADCASTS:
-                case MESSAGE_PROCESS_CONNECTION_CHANGE:
-                case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
-                    // All of these messages should be handled by parent state immediately.
-                    return false;
-
-                default:
-                    if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
-                    deferMessage(msg);
-            }
-            return true;
-        }
-    }
-
-    class SetAddresedPlayerAndPlayItem extends CmdState {
-        private static final String STATE_TAG = "AVRCPSM.SetAddresedPlayerAndPlayItem";
-        int mScope;
-        String mPlayItemId;
-        String mAddrPlayerId;
-
-        public void setItemAndScope(String addrPlayerId, String playItemId, int scope) {
-            mAddrPlayerId = addrPlayerId;
-            mPlayItemId = playItemId;
-            mScope = scope;
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
-            switch (msg.what) {
-                case MESSAGE_PROCESS_SET_ADDRESSED_PLAYER:
-                    // Set the new addressed player.
-                    mBrowseTree.setCurrentAddressedPlayer(mAddrPlayerId);
-
-                    // And now play the item.
-                    AvrcpControllerService.playItemNative(mRemoteDevice.getBluetoothAddress(),
-                            (byte) mScope, AvrcpControllerService.hexStringToByteUID(mPlayItemId),
-                            (int) 0);
-
-                    // Transition to connected state here.
-                    transitionTo(mConnected);
-                    break;
-
-                case MESSAGE_INTERNAL_CMD_TIMEOUT:
-                    transitionTo(mConnected);
-                    break;
-
-                case MESSAGE_SEND_PASS_THROUGH_CMD:
-                case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
-                case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
-                case MESSAGE_PROCESS_TRACK_CHANGED:
-                case MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
-                case MESSAGE_STOP_METADATA_BROADCASTS:
-                case MESSAGE_START_METADATA_BROADCASTS:
-                case MESSAGE_PROCESS_CONNECTION_CHANGE:
-                case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
-                    // All of these messages should be handled by parent state immediately.
-                    return false;
-
-                default:
-                    if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
-                    deferMessage(msg);
-            }
-            return true;
-        }
-    }
-
-    // Class template for commands. Each state should do the following:
-    // (a) In enter() send a timeout message which could be tracked in the
-    // processMessage() stage.
-    // (b) In exit() remove all the timeouts.
-    //
-    // Essentially the lifecycle of a timeout should be bounded to a CmdState always.
-    abstract class CmdState extends State {
-        @Override
-        public void enter() {
-            sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
         }
 
         @Override
         public void exit() {
             removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
+            mBrowseNode = null;
+            super.exit();
         }
     }
 
-    // Interface APIs
-    boolean isConnected() {
-        synchronized (mLock) {
-            return mIsConnected;
-        }
-    }
-
-    void doQuit() {
-        try {
-            mContext.unregisterReceiver(mBroadcastReceiver);
-        } catch (IllegalArgumentException expected) {
-            // If the receiver was never registered unregister will throw an
-            // IllegalArgumentException.
-        }
-        quit();
-    }
-
-    void dump(StringBuilder sb) {
-        ProfileService.println(sb, "StateMachine: " + this.toString());
-    }
-
-    MediaMetadata getCurrentMetaData() {
-        synchronized (mLock) {
-            if (mAddressedPlayer != null && mAddressedPlayer.getCurrentTrack() != null) {
-                MediaMetadata mmd = mAddressedPlayer.getCurrentTrack().getMediaMetaData();
-                if (DBG) {
-                    Log.d(TAG, "getCurrentMetaData mmd " + mmd);
-                }
-            }
-            return EMPTY_MEDIA_METADATA;
-        }
-    }
-
-    PlaybackState getCurrentPlayBackState() {
-        return getCurrentPlayBackState(true);
-    }
-
-    PlaybackState getCurrentPlayBackState(boolean cached) {
-        if (cached) {
-            synchronized (mLock) {
-                if (mAddressedPlayer == null) {
-                    return new PlaybackState.Builder().setState(PlaybackState.STATE_ERROR,
-                            PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0).build();
-                }
-                return mAddressedPlayer.getPlaybackState();
-            }
-        } else {
-            // Issue a native request, we return NULL since this is only for PTS.
-            AvrcpControllerService.getPlaybackStateNative(mRemoteDevice.getBluetoothAddress());
-            return null;
-        }
-    }
-
-    // Entry point to the state machine where the services should call to fetch children
-    // for a specific node. It checks if the currently browsed node is the same as the one being
-    // asked for, in that case it returns the currently cached children. This saves bandwidth and
-    // also if we are already fetching elements for a current folder (since we need to batch
-    // fetches) then we should not submit another request but simply return what we have fetched
-    // until now.
-    //
-    // It handles fetches to all VFS, Now Playing and Media Player lists.
-    void getChildren(String parentMediaId, int start, int items) {
-        BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(parentMediaId);
-        if (bn == null) {
-            Log.e(TAG, "Invalid folder to browse " + mBrowseTree);
-            broadcastFolderList(parentMediaId, EMPTY_MEDIA_ITEM_LIST);
-            return;
-        }
-
-        if (DBG) {
-            Log.d(TAG, "To Browse folder " + bn + " is cached " + bn.isCached() + " current folder "
-                    + mBrowseTree.getCurrentBrowsedFolder());
-        }
-        if (bn.equals(mBrowseTree.getCurrentBrowsedFolder()) && bn.isCached()) {
-            if (DBG) {
-                Log.d(TAG, "Same cached folder -- returning existing children.");
-            }
-            BrowseTree.BrowseNode n = mBrowseTree.findBrowseNodeByID(parentMediaId);
-            ArrayList<MediaItem> childrenList = new ArrayList<MediaItem>();
-            for (BrowseTree.BrowseNode cn : n.getChildren()) {
-                childrenList.add(cn.getMediaItem());
-            }
-            broadcastFolderList(parentMediaId, childrenList);
-            return;
-        }
-
-        Message msg = null;
-        int btDirection = mBrowseTree.getDirection(parentMediaId);
-        BrowseTree.BrowseNode currFol = mBrowseTree.getCurrentBrowsedFolder();
-        if (DBG) {
-            Log.d(TAG, "Browse direction parent " + mBrowseTree.getCurrentBrowsedFolder() + " req "
-                    + parentMediaId + " direction " + btDirection);
-        }
-        if (BrowseTree.ROOT.equals(parentMediaId)) {
-            // Root contains the list of players.
-            msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items);
-        } else if (bn.isPlayer() && btDirection != BrowseTree.DIRECTION_SAME) {
-            // Set browsed (and addressed player) as the new player.
-            // This should fetch the list of folders.
-            msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER,
-                    bn.getPlayerID(), 0, bn.getID());
-        } else if (bn.isNowPlaying()) {
-            // Issue a request to fetch the items.
-            msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST, start,
-                    items, parentMediaId);
-        } else {
-            // Only change folder if desired. If an app refreshes a folder
-            // (because it resumed etc) and current folder does not change
-            // then we can simply fetch list.
-
-            // We exempt two conditions from change folder:
-            // a) If the new folder is the same as current folder (refresh of UI)
-            // b) If the new folder is ROOT and current folder is NOW_PLAYING (or vice-versa)
-            // In this condition we 'fake' child-parent hierarchy but it does not exist in
-            // bluetooth world.
-            boolean isNowPlayingToRoot =
-                    currFol.isNowPlaying() && bn.getID().equals(BrowseTree.ROOT);
-            if (!isNowPlayingToRoot) {
-                // Find the direction of traversal.
-                int direction = -1;
-                if (DBG) Log.d(TAG, "Browse direction " + currFol + " " + bn + " = " + btDirection);
-                if (btDirection == BrowseTree.DIRECTION_UNKNOWN) {
-                    Log.w(TAG, "parent " + bn + " is not a direct "
-                            + "successor or predeccessor of current folder " + currFol);
-                    broadcastFolderList(parentMediaId, EMPTY_MEDIA_ITEM_LIST);
-                    return;
-                }
-
-                if (btDirection == BrowseTree.DIRECTION_DOWN) {
-                    direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN;
-                } else if (btDirection == BrowseTree.DIRECTION_UP) {
-                    direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP;
-                }
-
-                Bundle b = new Bundle();
-                b.putString(AvrcpControllerService.EXTRA_FOLDER_ID, bn.getID());
-                b.putString(AvrcpControllerService.EXTRA_FOLDER_BT_ID, bn.getFolderUID());
-                msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH,
-                        direction, 0, b);
-            } else {
-                // Fetch the listing without changing paths.
-                msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start,
-                        items, bn.getFolderUID());
-            }
-        }
-
-        if (msg != null) {
-            sendMessage(msg);
-        }
-    }
-
-    public void fetchAttrAndPlayItem(String uid) {
-        BrowseTree.BrowseNode currItem = mBrowseTree.findFolderByIDLocked(uid);
-        BrowseTree.BrowseNode currFolder = mBrowseTree.getCurrentBrowsedFolder();
-        if (DBG) Log.d(TAG, "fetchAttrAndPlayItem mediaId=" + uid + " node=" + currItem);
-        if (currItem != null) {
-            int scope = currFolder.isNowPlaying() ? AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING
-                    : AvrcpControllerService.BROWSE_SCOPE_VFS;
-            Message msg =
-                    obtainMessage(AvrcpControllerStateMachine.MESSAGE_FETCH_ATTR_AND_PLAY_ITEM,
-                            scope, 0, currItem.getFolderUID());
-            sendMessage(msg);
-        }
-    }
-
-    private void broadcastMetaDataChanged(MediaMetadata metadata) {
-        Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT);
-        intent.putExtra(AvrcpControllerService.EXTRA_METADATA, metadata);
-        if (VDBG) {
-            Log.d(TAG, " broadcastMetaDataChanged = " + metadata.getDescription());
-        }
-        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-    }
-
-    private void broadcastFolderList(String id, ArrayList<MediaItem> items) {
-        Intent intent = new Intent(AvrcpControllerService.ACTION_FOLDER_LIST);
-        if (VDBG) Log.d(TAG, "broadcastFolderList id " + id + " items " + items);
-        intent.putExtra(AvrcpControllerService.EXTRA_FOLDER_ID, id);
-        intent.putParcelableArrayListExtra(AvrcpControllerService.EXTRA_FOLDER_LIST, items);
-        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-    }
-
-    private void broadcastPlayBackStateChanged(PlaybackState state) {
-        Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT);
-        intent.putExtra(AvrcpControllerService.EXTRA_PLAYBACK, state);
-        if (DBG) {
-            Log.d(TAG, " broadcastPlayBackStateChanged = " + state.toString());
-        }
-        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-    }
-
-    private void setAbsVolume(int absVol, int label) {
-        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-        // Ignore first volume command since phone may not know difference between stream volume
-        // and amplifier volume.
-        if (mRemoteDevice.getFirstAbsVolCmdRecvd()) {
-            int newIndex = (maxVolume * absVol) / ABS_VOL_BASE;
-            if (DBG) {
-                Log.d(TAG, " setAbsVolume =" + absVol + " maxVol = " + maxVolume
-                        + " cur = " + currIndex + " new = " + newIndex);
-            }
-            /*
-             * In some cases change in percentage is not sufficient enough to warrant
-             * change in index values which are in range of 0-15. For such cases
-             * no action is required
-             */
-            if (newIndex != currIndex) {
-                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
-                        AudioManager.FLAG_SHOW_UI);
-            }
-        } else {
-            mRemoteDevice.setFirstAbsVolCmdRecvd();
-            absVol = (currIndex * ABS_VOL_BASE) / maxVolume;
-            if (DBG) Log.d(TAG, " SetAbsVol recvd for first time, respond with " + absVol);
-        }
-        AvrcpControllerService.sendAbsVolRspNative(mRemoteDevice.getBluetoothAddress(), absVol,
-                label);
-    }
-
-    private int getVolumePercentage() {
-        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-        int percentageVol = ((currIndex * ABS_VOL_BASE) / maxVolume);
-        return percentageVol;
-    }
-
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+    protected class Disconnecting extends State {
         @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
-                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
-                if (streamType == AudioManager.STREAM_MUSIC) {
-                    sendMessage(MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION);
-                }
+        public void enter() {
+            onBrowsingDisconnected();
+            broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
+            transitionTo(mDisconnected);
+        }
+    }
+
+    /**
+     * Handle a request to align our local volume with the volume of a remote device. If
+     * we're assuming the source volume is fixed then a response of ABS_VOL_MAX will always be
+     * sent and no volume adjustment action will be taken on the sink side.
+     *
+     * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX]
+     * @param label Volume notification label
+     */
+    private void handleAbsVolumeRequest(int absVol, int label) {
+        logD("handleAbsVolumeRequest: absVol = " + absVol + ", label = " + label);
+        if (mIsVolumeFixed) {
+            logD("Source volume is assumed to be fixed, responding with max volume");
+            absVol = ABS_VOL_BASE;
+        } else {
+            mVolumeChangedNotificationsToIgnore++;
+            removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
+            sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
+                    ABS_VOL_TIMEOUT_MILLIS);
+            setAbsVolume(absVol);
+        }
+        mService.sendAbsVolRspNative(mDeviceAddress, absVol, label);
+    }
+
+    /**
+     * Align our volume with a requested absolute volume level
+     *
+     * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX]
+     */
+    private void setAbsVolume(int absVol) {
+        int maxLocalVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        int curLocalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        int reqLocalVolume = (maxLocalVolume * absVol) / ABS_VOL_BASE;
+        logD("setAbsVolme: absVol = " + absVol + ", reqLocal = " + reqLocalVolume
+                + ", curLocal = " + curLocalVolume + ", maxLocal = " + maxLocalVolume);
+
+        /*
+         * In some cases change in percentage is not sufficient enough to warrant
+         * change in index values which are in range of 0-15. For such cases
+         * no action is required
+         */
+        if (reqLocalVolume != curLocalVolume) {
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, reqLocalVolume,
+                    AudioManager.FLAG_SHOW_UI);
+        }
+    }
+
+    private int getAbsVolume() {
+        if (mIsVolumeFixed) {
+            return ABS_VOL_BASE;
+        }
+        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        int newIndex = (currIndex * ABS_VOL_BASE) / maxVolume;
+        return newIndex;
+    }
+
+    MediaSessionCompat.Callback mSessionCallbacks = new MediaSessionCompat.Callback() {
+        @Override
+        public void onPlay() {
+            logD("onPlay");
+            onPrepare();
+            sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY);
+        }
+
+        @Override
+        public void onPause() {
+            logD("onPause");
+            sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
+        }
+
+        @Override
+        public void onSkipToNext() {
+            logD("onSkipToNext");
+            onPrepare();
+            sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD);
+        }
+
+        @Override
+        public void onSkipToPrevious() {
+            logD("onSkipToPrevious");
+            onPrepare();
+            sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD);
+        }
+
+        @Override
+        public void onSkipToQueueItem(long id) {
+            logD("onSkipToQueueItem" + id);
+            onPrepare();
+            BrowseTree.BrowseNode node = mBrowseTree.getTrackFromNowPlayingList((int) id);
+            if (node != null) {
+                sendMessage(MESSAGE_PLAY_ITEM, node);
             }
         }
+
+        @Override
+        public void onStop() {
+            logD("onStop");
+            sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_STOP);
+        }
+
+        @Override
+        public void onPrepare() {
+            logD("onPrepare");
+            A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
+            if (a2dpSinkService != null) {
+                a2dpSinkService.requestAudioFocus(mDevice, true);
+            }
+        }
+
+        @Override
+        public void onRewind() {
+            logD("onRewind");
+            sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_REWIND);
+        }
+
+        @Override
+        public void onFastForward() {
+            logD("onFastForward");
+            sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FF);
+        }
+
+        @Override
+        public void onPlayFromMediaId(String mediaId, Bundle extras) {
+            logD("onPlayFromMediaId");
+            // Play the item if possible.
+            onPrepare();
+            BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId);
+            sendMessage(MESSAGE_PLAY_ITEM, node);
+        }
+
+        @Override
+        public void onSetRepeatMode(int repeatMode) {
+            logD("onSetRepeatMode");
+            sendMessage(MSG_AVRCP_SET_REPEAT, repeatMode);
+        }
+
+        @Override
+        public void onSetShuffleMode(int shuffleMode) {
+            logD("onSetShuffleMode");
+            sendMessage(MSG_AVRCP_SET_SHUFFLE, shuffleMode);
+
+        }
     };
 
-    public static String dumpMessageString(int message) {
-        String str = "UNKNOWN";
-        switch (message) {
-            case MESSAGE_SEND_PASS_THROUGH_CMD:
-                str = "REQ_PASS_THROUGH_CMD";
-                break;
-            case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
-                str = "REQ_GRP_NAV_CMD";
-                break;
-            case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                str = "CB_SET_ABS_VOL_CMD";
-                break;
-            case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
-                str = "CB_REGISTER_ABS_VOL";
-                break;
-            case MESSAGE_PROCESS_TRACK_CHANGED:
-                str = "CB_TRACK_CHANGED";
-                break;
-            case MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                str = "CB_PLAY_POS_CHANGED";
-                break;
-            case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                str = "CB_PLAY_STATUS_CHANGED";
-                break;
-            case MESSAGE_PROCESS_RC_FEATURES:
-                str = "CB_RC_FEATURES";
-                break;
-            case MESSAGE_PROCESS_CONNECTION_CHANGE:
-                str = "CB_CONN_CHANGED";
-                break;
-            default:
-                str = Integer.toString(message);
-                break;
+    protected void broadcastConnectionStateChanged(int currentState) {
+        if (mMostRecentState == currentState) {
+            return;
         }
-        return str;
+        if (currentState == BluetoothProfile.STATE_CONNECTED) {
+            MetricsLogger.logProfileConnectionEvent(
+                    BluetoothMetricsProto.ProfileId.AVRCP_CONTROLLER);
+        }
+        logD("Connection state " + mDevice + ": " + mMostRecentState + "->" + currentState);
+        Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, mMostRecentState);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, currentState);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        mMostRecentState = currentState;
+        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
     }
 
-    public static String displayBluetoothAvrcpSettings(BluetoothAvrcpPlayerSettings mSett) {
-        StringBuffer sb = new StringBuffer();
-        int supportedSetting = mSett.getSettings();
-        if (VDBG) {
-            Log.d(TAG, " setting: " + supportedSetting);
-        }
-        if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) {
-            sb.append(" EQ : ");
-            sb.append(Integer.toString(mSett.getSettingValue(
-                    BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER)));
-        }
-        if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) {
-            sb.append(" REPEAT : ");
-            sb.append(Integer.toString(mSett.getSettingValue(
-                    BluetoothAvrcpPlayerSettings.SETTING_REPEAT)));
-        }
-        if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) {
-            sb.append(" SHUFFLE : ");
-            sb.append(Integer.toString(mSett.getSettingValue(
-                    BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE)));
-        }
-        if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) {
-            sb.append(" SCAN : ");
-            sb.append(Integer.toString(mSett.getSettingValue(
-                    BluetoothAvrcpPlayerSettings.SETTING_SCAN)));
-        }
-        return sb.toString();
+    private boolean shouldRequestFocus() {
+        return mService.getResources()
+                .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus);
     }
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
index d9e4924..2ef3c14 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -16,33 +16,69 @@
 
 package com.android.bluetooth.avrcpcontroller;
 
-import android.media.session.PlaybackState;
+import android.media.MediaMetadata;
+import android.os.SystemClock;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
 
+import java.util.Arrays;
+
 /*
  * Contains information about remote player
  */
 class AvrcpPlayer {
     private static final String TAG = "AvrcpPlayer";
-    private static final boolean DBG = true;
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
     public static final int INVALID_ID = -1;
 
-    private int mPlayStatus = PlaybackState.STATE_NONE;
-    private long mPlayTime = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
+    public static final int FEATURE_PLAY = 40;
+    public static final int FEATURE_STOP = 41;
+    public static final int FEATURE_PAUSE = 42;
+    public static final int FEATURE_REWIND = 44;
+    public static final int FEATURE_FAST_FORWARD = 45;
+    public static final int FEATURE_FORWARD = 47;
+    public static final int FEATURE_PREVIOUS = 48;
+    public static final int FEATURE_BROWSING = 59;
+
+    private int mPlayStatus = PlaybackStateCompat.STATE_NONE;
+    private long mPlayTime = PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN;
+    private long mPlayTimeUpdate = 0;
+    private float mPlaySpeed = 1;
     private int mId;
     private String mName = "";
     private int mPlayerType;
-    private TrackInfo mCurrentTrack = new TrackInfo();
+    private byte[] mPlayerFeatures = new byte[16];
+    private long mAvailableActions;
+    private MediaMetadata mCurrentTrack;
+    private PlaybackStateCompat mPlaybackStateCompat;
+    private PlayerApplicationSettings mSupportedPlayerApplicationSettings =
+            new PlayerApplicationSettings();
+    private PlayerApplicationSettings mCurrentPlayerApplicationSettings;
 
     AvrcpPlayer() {
         mId = INVALID_ID;
+        //Set Default Actions in case Player data isn't available.
+        mAvailableActions = PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY
+                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
+                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
+                | PlaybackStateCompat.ACTION_STOP;
+        PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
+                .setActions(mAvailableActions);
+        mPlaybackStateCompat = playbackStateBuilder.build();
     }
 
-    AvrcpPlayer(int id, String name, int transportFlags, int playStatus, int playerType) {
+    AvrcpPlayer(int id, String name, byte[] playerFeatures, int playStatus, int playerType) {
         mId = id;
         mName = name;
+        mPlayStatus = playStatus;
         mPlayerType = playerType;
+        mPlayerFeatures = Arrays.copyOf(playerFeatures, playerFeatures.length);
+        PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
+                .setActions(mAvailableActions);
+        mPlaybackStateCompat = playbackStateBuilder.build();
+        updateAvailableActions();
     }
 
     public int getId() {
@@ -55,6 +91,10 @@
 
     public void setPlayTime(int playTime) {
         mPlayTime = playTime;
+        mPlayTimeUpdate = SystemClock.elapsedRealtime();
+        mPlaybackStateCompat = new PlaybackStateCompat.Builder(mPlaybackStateCompat).setState(
+                mPlayStatus, mPlayTime,
+                mPlaySpeed).build();
     }
 
     public long getPlayTime() {
@@ -62,39 +102,117 @@
     }
 
     public void setPlayStatus(int playStatus) {
+        mPlayTime += mPlaySpeed * (SystemClock.elapsedRealtime()
+                - mPlaybackStateCompat.getLastPositionUpdateTime());
         mPlayStatus = playStatus;
+        switch (mPlayStatus) {
+            case PlaybackStateCompat.STATE_STOPPED:
+                mPlaySpeed = 0;
+                break;
+            case PlaybackStateCompat.STATE_PLAYING:
+                mPlaySpeed = 1;
+                break;
+            case PlaybackStateCompat.STATE_PAUSED:
+                mPlaySpeed = 0;
+                break;
+            case PlaybackStateCompat.STATE_FAST_FORWARDING:
+                mPlaySpeed = 3;
+                break;
+            case PlaybackStateCompat.STATE_REWINDING:
+                mPlaySpeed = -3;
+                break;
+        }
+
+        mPlaybackStateCompat = new PlaybackStateCompat.Builder(mPlaybackStateCompat).setState(
+                mPlayStatus, mPlayTime,
+                mPlaySpeed).build();
     }
 
-    public PlaybackState getPlaybackState() {
+    public void setSupportedPlayerApplicationSettings(
+            PlayerApplicationSettings playerApplicationSettings) {
+        mSupportedPlayerApplicationSettings = playerApplicationSettings;
+        updateAvailableActions();
+    }
+
+    public void setCurrentPlayerApplicationSettings(
+            PlayerApplicationSettings playerApplicationSettings) {
+        Log.d(TAG, "Settings changed");
+        mCurrentPlayerApplicationSettings = playerApplicationSettings;
+        MediaSessionCompat session = BluetoothMediaBrowserService.getSession();
+        session.setRepeatMode(mCurrentPlayerApplicationSettings.getSetting(
+                PlayerApplicationSettings.REPEAT_STATUS));
+        session.setShuffleMode(mCurrentPlayerApplicationSettings.getSetting(
+                PlayerApplicationSettings.SHUFFLE_STATUS));
+    }
+
+    public int getPlayStatus() {
+        return mPlayStatus;
+    }
+
+    public boolean supportsFeature(int featureId) {
+        int byteNumber = featureId / 8;
+        byte bitMask = (byte) (1 << (featureId % 8));
+        return (mPlayerFeatures[byteNumber] & bitMask) == bitMask;
+    }
+
+    public boolean supportsSetting(int settingType, int settingValue) {
+        return mSupportedPlayerApplicationSettings.supportsSetting(settingType, settingValue);
+    }
+
+    public PlaybackStateCompat getPlaybackState() {
         if (DBG) {
             Log.d(TAG, "getPlayBackState state " + mPlayStatus + " time " + mPlayTime);
         }
-
-        long position = mPlayTime;
-        float speed = 1;
-        switch (mPlayStatus) {
-            case PlaybackState.STATE_STOPPED:
-                position = 0;
-                speed = 0;
-                break;
-            case PlaybackState.STATE_PAUSED:
-                speed = 0;
-                break;
-            case PlaybackState.STATE_FAST_FORWARDING:
-                speed = 3;
-                break;
-            case PlaybackState.STATE_REWINDING:
-                speed = -3;
-                break;
-        }
-        return new PlaybackState.Builder().setState(mPlayStatus, position, speed).build();
+        return mPlaybackStateCompat;
     }
 
-    public synchronized void updateCurrentTrack(TrackInfo update) {
+    public synchronized void updateCurrentTrack(MediaMetadata update) {
+        if (update != null) {
+            long trackNumber = update.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
+            mPlaybackStateCompat = new PlaybackStateCompat.Builder(
+                    mPlaybackStateCompat).setActiveQueueItemId(
+                    trackNumber - 1).build();
+        }
         mCurrentTrack = update;
     }
 
-    public synchronized TrackInfo getCurrentTrack() {
+    public synchronized MediaMetadata getCurrentTrack() {
         return mCurrentTrack;
     }
+
+    private void updateAvailableActions() {
+        if (supportsFeature(FEATURE_PLAY)) {
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_PLAY;
+        }
+        if (supportsFeature(FEATURE_STOP)) {
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_STOP;
+        }
+        if (supportsFeature(FEATURE_PAUSE)) {
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_PAUSE;
+        }
+        if (supportsFeature(FEATURE_REWIND)) {
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_REWIND;
+        }
+        if (supportsFeature(FEATURE_FAST_FORWARD)) {
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_FAST_FORWARD;
+        }
+        if (supportsFeature(FEATURE_FORWARD)) {
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
+        }
+        if (supportsFeature(FEATURE_PREVIOUS)) {
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
+        }
+        if (mSupportedPlayerApplicationSettings.supportsSetting(
+                PlayerApplicationSettings.REPEAT_STATUS)) {
+            mAvailableActions |= PlaybackStateCompat.ACTION_SET_REPEAT_MODE;
+        }
+        if (mSupportedPlayerApplicationSettings.supportsSetting(
+                PlayerApplicationSettings.SHUFFLE_STATUS)) {
+            mAvailableActions |= PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE;
+        }
+        mPlaybackStateCompat = new PlaybackStateCompat.Builder(mPlaybackStateCompat)
+                .setActions(mAvailableActions).build();
+
+        if (DBG) Log.d(TAG, "Supported Actions = " + mAvailableActions);
+    }
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
new file mode 100644
index 0000000..774f953
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.os.Bundle;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
+
+import androidx.media.MediaBrowserServiceCompat;
+
+import com.android.bluetooth.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implements the MediaBrowserService interface to AVRCP and A2DP
+ *
+ * This service provides a means for external applications to access A2DP and AVRCP.
+ * The applications are expected to use MediaBrowser (see API) and all the music
+ * browsing/playback/metadata can be controlled via MediaBrowser and MediaController.
+ *
+ * The current behavior of MediaSessionCompat exposed by this service is as follows:
+ * 1. MediaSessionCompat is active (i.e. SystemUI and other overview UIs can see updates) when
+ * device is connected and first starts playing. Before it starts playing we do not activate the
+ * session.
+ * 1.1 The session is active throughout the duration of connection.
+ * 2. The session is de-activated when the device disconnects. It will be connected again when (1)
+ * happens.
+ */
+public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat {
+    private static final String TAG = "BluetoothMediaBrowserService";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static BluetoothMediaBrowserService sBluetoothMediaBrowserService;
+
+    private MediaSessionCompat mSession;
+
+    // Browsing related structures.
+    private List<MediaSessionCompat.QueueItem> mMediaQueue = new ArrayList<>();
+
+    /**
+     * Initialize this BluetoothMediaBrowserService, creating our MediaSessionCompat, MediaPlayer
+     * and MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService.
+     */
+    @Override
+    public void onCreate() {
+        if (DBG) Log.d(TAG, "onCreate");
+        super.onCreate();
+
+        // Create and configure the MediaSessionCompat
+        mSession = new MediaSessionCompat(this, TAG);
+        setSessionToken(mSession.getSessionToken());
+        mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
+                | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
+        mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name));
+        mSession.setQueue(mMediaQueue);
+        PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
+        playbackStateBuilder.setState(PlaybackStateCompat.STATE_ERROR,
+                PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f).setActions(0);
+        playbackStateBuilder.setErrorMessage(getString(R.string.bluetooth_disconnected));
+        mSession.setPlaybackState(playbackStateBuilder.build());
+        sBluetoothMediaBrowserService = this;
+    }
+
+    List<MediaItem> getContents(final String parentMediaId) {
+        AvrcpControllerService avrcpControllerService =
+                AvrcpControllerService.getAvrcpControllerService();
+        if (avrcpControllerService == null) {
+            return new ArrayList(0);
+        } else {
+            return avrcpControllerService.getContents(parentMediaId);
+        }
+    }
+
+    @Override
+    public synchronized void onLoadChildren(final String parentMediaId,
+            final Result<List<MediaBrowserCompat.MediaItem>> result) {
+        if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
+        List<MediaBrowserCompat.MediaItem> contents =
+                MediaBrowserCompat.MediaItem.fromMediaItemList(getContents(parentMediaId));
+        if (contents == null) {
+            result.detach();
+        } else {
+            result.sendResult(contents);
+        }
+    }
+
+    @Override
+    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+        if (DBG) Log.d(TAG, "onGetRoot");
+        return new BrowserRoot(BrowseTree.ROOT, null);
+    }
+
+    private void updateNowPlayingQueue(BrowseTree.BrowseNode node) {
+        List<MediaItem> songList = node.getContents();
+        mMediaQueue.clear();
+        if (songList != null) {
+            for (MediaItem song : songList) {
+                mMediaQueue.add(new MediaSessionCompat.QueueItem(
+                        MediaDescriptionCompat.fromMediaDescription(song.getDescription()),
+                        mMediaQueue.size()));
+            }
+        }
+        mSession.setQueue(mMediaQueue);
+    }
+
+    static synchronized void notifyChanged(BrowseTree.BrowseNode node) {
+        if (sBluetoothMediaBrowserService != null) {
+            if (node.getScope() == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
+                sBluetoothMediaBrowserService.updateNowPlayingQueue(node);
+            } else {
+                sBluetoothMediaBrowserService.notifyChildrenChanged(node.getID());
+            }
+        }
+    }
+
+    static synchronized void addressedPlayerChanged(MediaSessionCompat.Callback callback) {
+        if (sBluetoothMediaBrowserService != null) {
+            sBluetoothMediaBrowserService.mSession.setCallback(callback);
+        } else {
+            Log.w(TAG, "addressedPlayerChanged Unavailable");
+        }
+    }
+
+    static synchronized void trackChanged(MediaMetadata mediaMetadata) {
+        if (sBluetoothMediaBrowserService != null) {
+            sBluetoothMediaBrowserService.mSession.setMetadata(
+                    MediaMetadataCompat.fromMediaMetadata(mediaMetadata));
+        } else {
+            Log.w(TAG, "trackChanged Unavailable");
+        }
+    }
+
+    static synchronized void notifyChanged(PlaybackStateCompat playbackState) {
+        Log.d(TAG, "notifyChanged PlaybackState" + playbackState);
+        if (sBluetoothMediaBrowserService != null) {
+            sBluetoothMediaBrowserService.mSession.setPlaybackState(playbackState);
+        } else {
+            Log.w(TAG, "notifyChanged Unavailable");
+        }
+    }
+
+    /**
+     * Send AVRCP Play command
+     */
+    public static synchronized void play() {
+        if (sBluetoothMediaBrowserService != null) {
+            sBluetoothMediaBrowserService.mSession.getController().getTransportControls().play();
+        } else {
+            Log.w(TAG, "play Unavailable");
+        }
+    }
+
+    /**
+     * Send AVRCP Pause command
+     */
+    public static synchronized void pause() {
+        if (sBluetoothMediaBrowserService != null) {
+            sBluetoothMediaBrowserService.mSession.getController().getTransportControls().pause();
+        } else {
+            Log.w(TAG, "pause Unavailable");
+        }
+    }
+
+    /**
+     * Get object for controlling playback
+     */
+    public static synchronized MediaControllerCompat.TransportControls getTransportControls() {
+        if (sBluetoothMediaBrowserService != null) {
+            return sBluetoothMediaBrowserService.mSession.getController().getTransportControls();
+        } else {
+            Log.w(TAG, "transportControls Unavailable");
+            return null;
+        }
+    }
+
+    /**
+     * Set Media session active whenever we have Focus of any kind
+     */
+    public static synchronized void setActive(boolean active) {
+        if (sBluetoothMediaBrowserService != null) {
+            sBluetoothMediaBrowserService.mSession.setActive(active);
+        } else {
+            Log.w(TAG, "setActive Unavailable");
+        }
+    }
+
+    /**
+     * Get Media session for updating state
+     */
+    public static synchronized MediaSessionCompat getSession() {
+        if (sBluetoothMediaBrowserService != null) {
+            return sBluetoothMediaBrowserService.mSession;
+        } else {
+            Log.w(TAG, "getSession Unavailable");
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index 4482bd7..accea2a 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -16,16 +16,17 @@
 
 package com.android.bluetooth.avrcpcontroller;
 
+import android.bluetooth.BluetoothDevice;
 import android.media.MediaDescription;
 import android.media.browse.MediaBrowser;
 import android.media.browse.MediaBrowser.MediaItem;
 import android.os.Bundle;
-import android.service.media.MediaBrowserService.Result;
 import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.UUID;
 
 // Browsing hierarchy.
 // Root:
@@ -40,15 +41,11 @@
 //      ....
 public class BrowseTree {
     private static final String TAG = "BrowseTree";
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false;
-
-    public static final int DIRECTION_DOWN = 0;
-    public static final int DIRECTION_UP = 1;
-    public static final int DIRECTION_SAME = 2;
-    public static final int DIRECTION_UNKNOWN = -1;
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
 
     public static final String ROOT = "__ROOT__";
+    public static final String UP = "__UP__";
     public static final String NOW_PLAYING_PREFIX = "NOW_PLAYING";
     public static final String PLAYER_PREFIX = "PLAYER";
 
@@ -57,19 +54,39 @@
     private BrowseNode mCurrentBrowseNode;
     private BrowseNode mCurrentBrowsedPlayer;
     private BrowseNode mCurrentAddressedPlayer;
+    private int mDepth = 0;
+    final BrowseNode mRootNode;
+    final BrowseNode mNavigateUpNode;
+    final BrowseNode mNowPlayingNode;
 
-    BrowseTree() {
-    }
+    BrowseTree(BluetoothDevice device) {
+        if (device == null) {
+            mRootNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+                    .setMediaId(ROOT).setTitle(ROOT).build(), MediaItem.FLAG_BROWSABLE));
+            mRootNode.setCached(true);
+        } else {
+            mRootNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+                    .setMediaId(ROOT + device.getAddress().toString()).setTitle(
+                            device.getName()).build(), MediaItem.FLAG_BROWSABLE));
+            mRootNode.mDevice = device;
 
-    public void init() {
-        MediaDescription.Builder mdb = new MediaDescription.Builder();
-        mdb.setMediaId(ROOT);
-        mdb.setTitle(ROOT);
-        Bundle mdBundle = new Bundle();
-        mdBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, ROOT);
-        mdb.setExtras(mdBundle);
-        mBrowseMap.put(ROOT, new BrowseNode(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE)));
-        mCurrentBrowseNode = mBrowseMap.get(ROOT);
+        }
+        mRootNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST;
+        mRootNode.setExpectedChildren(255);
+
+        mNavigateUpNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+                .setMediaId(UP).setTitle(UP).build(),
+                MediaItem.FLAG_BROWSABLE));
+
+        mNowPlayingNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+                .setMediaId(NOW_PLAYING_PREFIX)
+                .setTitle(NOW_PLAYING_PREFIX).build(), MediaItem.FLAG_BROWSABLE));
+        mNowPlayingNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING;
+        mNowPlayingNode.setExpectedChildren(255);
+        mBrowseMap.put(ROOT, mRootNode);
+        mBrowseMap.put(NOW_PLAYING_PREFIX, mNowPlayingNode);
+
+        mCurrentBrowseNode = mRootNode;
     }
 
     public void clear() {
@@ -77,11 +94,23 @@
         mBrowseMap.clear();
     }
 
+    void onConnected(BluetoothDevice device) {
+        BrowseNode browseNode = new BrowseNode(device);
+        mRootNode.addChild(browseNode);
+    }
+
+    BrowseNode getTrackFromNowPlayingList(int trackNumber) {
+        return mNowPlayingNode.mChildren.get(trackNumber);
+    }
+
     // Each node of the tree is represented by Folder ID, Folder Name and the children.
     class BrowseNode {
         // MediaItem to store the media related details.
         MediaItem mItem;
 
+        BluetoothDevice mDevice;
+        long mBluetoothId;
+
         // Type of this browse node.
         // Since Media APIs do not define the player separately we define that
         // distinction here.
@@ -91,15 +120,19 @@
         // without doing another fetch.
         boolean mCached = false;
 
-        // Result object if this node is not loaded yet. This result object will be used
-        // once loading is finished.
-        Result<List<MediaItem>> mResult = null;
+        byte mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_VFS;
 
         // List of children.
-        final List<BrowseNode> mChildren = new ArrayList<BrowseNode>();
+        private BrowseNode mParent;
+        private final List<BrowseNode> mChildren = new ArrayList<BrowseNode>();
+        private int mExpectedChildrenCount;
 
         BrowseNode(MediaItem item) {
             mItem = item;
+            Bundle extras = mItem.getDescription().getExtras();
+            if (extras != null) {
+                mBluetoothId = extras.getLong(AvrcpControllerService.MEDIA_ITEM_UID_KEY);
+            }
         }
 
         BrowseNode(AvrcpPlayer player) {
@@ -107,34 +140,120 @@
 
             // Transform the player into a item.
             MediaDescription.Builder mdb = new MediaDescription.Builder();
-            Bundle mdExtra = new Bundle();
             String playerKey = PLAYER_PREFIX + player.getId();
-            mdExtra.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, playerKey);
-            mdb.setExtras(mdExtra);
-            mdb.setMediaId(playerKey);
+            mBluetoothId = player.getId();
+
+            mdb.setMediaId(UUID.randomUUID().toString());
             mdb.setTitle(player.getName());
+            int mediaItemFlags = player.supportsFeature(AvrcpPlayer.FEATURE_BROWSING)
+                    ? MediaBrowser.MediaItem.FLAG_BROWSABLE : 0;
+            mItem = new MediaBrowser.MediaItem(mdb.build(), mediaItemFlags);
+        }
+
+        BrowseNode(BluetoothDevice device) {
+            boolean mIsPlayer = true;
+            mDevice = device;
+            MediaDescription.Builder mdb = new MediaDescription.Builder();
+            String playerKey = PLAYER_PREFIX + device.getAddress().toString();
+            mdb.setMediaId(playerKey);
+            mdb.setTitle(device.getName());
+            int mediaItemFlags = MediaBrowser.MediaItem.FLAG_BROWSABLE;
+            mItem = new MediaBrowser.MediaItem(mdb.build(), mediaItemFlags);
+        }
+
+        private BrowseNode(String name) {
+            MediaDescription.Builder mdb = new MediaDescription.Builder();
+            mdb.setMediaId(name);
+            mdb.setTitle(name);
             mItem = new MediaBrowser.MediaItem(mdb.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
         }
 
+        synchronized void setExpectedChildren(int count) {
+            mExpectedChildrenCount = count;
+        }
+
+        synchronized int getExpectedChildren() {
+            return mExpectedChildrenCount;
+        }
+
+        synchronized <E> int addChildren(List<E> newChildren) {
+            for (E child : newChildren) {
+                BrowseNode currentNode = null;
+                if (child instanceof MediaItem) {
+                    currentNode = new BrowseNode((MediaItem) child);
+                } else if (child instanceof AvrcpPlayer) {
+                    currentNode = new BrowseNode((AvrcpPlayer) child);
+                }
+                addChild(currentNode);
+            }
+            return newChildren.size();
+        }
+
+        synchronized boolean addChild(BrowseNode node) {
+            if (node != null) {
+                node.mParent = this;
+                if (this.mBrowseScope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
+                    node.mBrowseScope = this.mBrowseScope;
+                }
+                if (node.mDevice == null) {
+                    node.mDevice = this.mDevice;
+                }
+                mChildren.add(node);
+                mBrowseMap.put(node.getID(), node);
+                return true;
+            }
+            return false;
+        }
+
+        synchronized void removeChild(BrowseNode node) {
+            mChildren.remove(node);
+            mBrowseMap.remove(node.getID());
+        }
+
+        synchronized int getChildrenCount() {
+            return mChildren.size();
+        }
+
         synchronized List<BrowseNode> getChildren() {
             return mChildren;
         }
 
-        synchronized boolean isChild(BrowseNode node) {
-            for (BrowseNode bn : mChildren) {
-                if (bn.equals(node)) {
-                    return true;
+        synchronized BrowseNode getParent() {
+            return mParent;
+        }
+
+        synchronized List<MediaItem> getContents() {
+            if (mChildren.size() > 0 || mCached) {
+                List<MediaItem> contents = new ArrayList<MediaItem>(mChildren.size());
+                for (BrowseNode child : mChildren) {
+                    contents.add(child.getMediaItem());
                 }
+                return contents;
             }
-            return false;
+            return null;
+        }
+
+        synchronized boolean isChild(BrowseNode node) {
+            return mChildren.contains(node);
         }
 
         synchronized boolean isCached() {
             return mCached;
         }
 
+        synchronized boolean isBrowsable() {
+            return mItem.isBrowsable();
+        }
+
         synchronized void setCached(boolean cached) {
+            if (DBG) Log.d(TAG, "Set Cache" + cached + "Node" + toString());
             mCached = cached;
+            if (!cached) {
+                for (BrowseNode child : mChildren) {
+                    mBrowseMap.remove(child.getID());
+                }
+                mChildren.clear();
+            }
         }
 
         // Fetch the Unique UID for this item, this is unique across all elements in the tree.
@@ -147,13 +266,19 @@
             return Integer.parseInt(getID().replace(PLAYER_PREFIX, ""));
         }
 
+        synchronized byte getScope() {
+            return mBrowseScope;
+        }
+
         // Fetch the Folder UID that can be used to fetch folder listing via bluetooth.
         // This may not be unique hence this combined with direction will define the
         // browsing here.
         synchronized String getFolderUID() {
-            return mItem.getDescription()
-                    .getExtras()
-                    .getString(AvrcpControllerService.MEDIA_ITEM_UID_KEY);
+            return getID();
+        }
+
+        synchronized long getBluetoothID() {
+            return mBluetoothId;
         }
 
         synchronized MediaItem getMediaItem() {
@@ -178,52 +303,24 @@
         }
 
         @Override
-        public String toString() {
+        public synchronized String toString() {
             if (VDBG) {
-                return "ID: " + getID() + " desc: " + mItem;
+                String serialized = "[ Name: " + mItem.getDescription().getTitle()
+                        + " Scope:" + mBrowseScope + " expected Children: "
+                        + mExpectedChildrenCount + "] ";
+                for (BrowseNode node : mChildren) {
+                    serialized += node.toString();
+                }
+                return serialized;
             } else {
                 return "ID: " + getID();
             }
         }
-    }
 
-    synchronized <E> void refreshChildren(String parentID, List<E> children) {
-        BrowseNode parent = findFolderByIDLocked(parentID);
-        if (parent == null) {
-            Log.w(TAG, "parent not found for parentID " + parentID);
-            return;
+        // Returns true if target is a descendant of this.
+        synchronized boolean isDescendant(BrowseNode target) {
+            return getEldestChild(this, target) == null ? false : true;
         }
-        refreshChildren(parent, children);
-    }
-
-    synchronized <E> void refreshChildren(BrowseNode parent, List<E> children) {
-        if (children == null) {
-            Log.e(TAG, "children cannot be null ");
-            return;
-        }
-
-        List<BrowseNode> bnList = new ArrayList<BrowseNode>();
-        for (E child : children) {
-            if (child instanceof MediaItem) {
-                bnList.add(new BrowseNode((MediaItem) child));
-            } else if (child instanceof AvrcpPlayer) {
-                bnList.add(new BrowseNode((AvrcpPlayer) child));
-            }
-        }
-
-        String parentID = parent.getID();
-        // Make sure that the child list is clean.
-        if (VDBG) {
-            Log.d(TAG, "parent " + parentID + " child list " + parent.getChildren());
-        }
-
-        addChildrenLocked(parent, bnList);
-        List<MediaItem> childrenList = new ArrayList<MediaItem>();
-        for (BrowseNode bn : parent.getChildren()) {
-            childrenList.add(bn.getMediaItem());
-        }
-
-        parent.setCached(true);
     }
 
     synchronized BrowseNode findBrowseNodeByID(String parentID) {
@@ -233,50 +330,13 @@
             return null;
         }
         if (VDBG) {
-            Log.d(TAG, "Browse map: " + mBrowseMap);
+            Log.d(TAG, "Size" + mBrowseMap.size());
         }
         return bn;
     }
 
-    BrowseNode findFolderByIDLocked(String parentID) {
-        return mBrowseMap.get(parentID);
-    }
-
-    void addChildrenLocked(BrowseNode parent, List<BrowseNode> items) {
-        // Remove existing children and then add the new children.
-        for (BrowseNode c : parent.getChildren()) {
-            mBrowseMap.remove(c.getID());
-        }
-        parent.getChildren().clear();
-
-        for (BrowseNode bn : items) {
-            parent.getChildren().add(bn);
-            mBrowseMap.put(bn.getID(), bn);
-        }
-    }
-
-    synchronized int getDirection(String toUID) {
-        BrowseNode fromFolder = mCurrentBrowseNode;
-        BrowseNode toFolder = findFolderByIDLocked(toUID);
-        if (fromFolder == null || toFolder == null) {
-            Log.e(TAG, "from folder " + mCurrentBrowseNode + " or to folder " + toUID + " null!");
-        }
-
-        // Check the relationship.
-        if (fromFolder.isChild(toFolder)) {
-            return DIRECTION_DOWN;
-        } else if (toFolder.isChild(fromFolder)) {
-            return DIRECTION_UP;
-        } else if (fromFolder.equals(toFolder)) {
-            return DIRECTION_SAME;
-        } else {
-            Log.w(TAG, "from folder " + mCurrentBrowseNode + "to folder " + toUID);
-            return DIRECTION_UNKNOWN;
-        }
-    }
-
     synchronized boolean setCurrentBrowsedFolder(String uid) {
-        BrowseNode bn = findFolderByIDLocked(uid);
+        BrowseNode bn = mBrowseMap.get(uid);
         if (bn == null) {
             Log.e(TAG, "Setting an unknown browsed folder, ignoring bn " + uid);
             return false;
@@ -284,10 +344,8 @@
 
         // Set the previous folder as not cached so that we fetch the contents again.
         if (!bn.equals(mCurrentBrowseNode)) {
-            Log.d(TAG, "Set cache false " + bn + " curr " + mCurrentBrowseNode);
-            mCurrentBrowseNode.setCached(false);
+            Log.d(TAG, "Set cache  " + bn + " curr " + mCurrentBrowseNode);
         }
-
         mCurrentBrowseNode = bn;
         return true;
     }
@@ -296,13 +354,22 @@
         return mCurrentBrowseNode;
     }
 
-    synchronized boolean setCurrentBrowsedPlayer(String uid) {
-        BrowseNode bn = findFolderByIDLocked(uid);
+    synchronized boolean setCurrentBrowsedPlayer(String uid, int items, int depth) {
+        BrowseNode bn = mBrowseMap.get(uid);
         if (bn == null) {
             Log.e(TAG, "Setting an unknown browsed player, ignoring bn " + uid);
             return false;
         }
         mCurrentBrowsedPlayer = bn;
+        mCurrentBrowseNode = mCurrentBrowsedPlayer;
+        for (Integer level = 0; level < depth; level++) {
+            BrowseNode dummyNode = new BrowseNode(level.toString());
+            dummyNode.mParent = mCurrentBrowseNode;
+            dummyNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_VFS;
+            mCurrentBrowseNode = dummyNode;
+        }
+        mCurrentBrowseNode.setExpectedChildren(items);
+        mDepth = depth;
         return true;
     }
 
@@ -311,9 +378,12 @@
     }
 
     synchronized boolean setCurrentAddressedPlayer(String uid) {
-        BrowseNode bn = findFolderByIDLocked(uid);
+        BrowseNode bn = mBrowseMap.get(uid);
         if (bn == null) {
-            Log.e(TAG, "Setting an unknown addressed player, ignoring bn " + uid);
+            if (DBG) Log.d(TAG, "Setting an unknown addressed player, ignoring bn " + uid);
+            mRootNode.setCached(false);
+            mRootNode.mChildren.add(mNowPlayingNode);
+            mBrowseMap.put(NOW_PLAYING_PREFIX, mNowPlayingNode);
             return false;
         }
         mCurrentAddressedPlayer = bn;
@@ -326,6 +396,58 @@
 
     @Override
     public String toString() {
-        return mBrowseMap.toString();
+        String serialized = "Size: " + mBrowseMap.size();
+        if (VDBG) {
+            serialized += mRootNode.toString();
+        }
+        return serialized;
+    }
+
+    // Calculates the path to target node.
+    // Returns: UP node to go up
+    // Returns: target node if there
+    // Returns: named node to go down
+    // Returns: null node if unknown
+    BrowseNode getNextStepToFolder(BrowseNode target) {
+        if (target == null) {
+            return null;
+        } else if (target.equals(mCurrentBrowseNode)
+                || target.equals(mNowPlayingNode)
+                || target.equals(mRootNode)) {
+            return target;
+        } else if (target.isPlayer()) {
+            if (mDepth > 0) {
+                mDepth--;
+                return mNavigateUpNode;
+            } else {
+                return target;
+            }
+        } else if (mBrowseMap.get(target.getID()) == null) {
+            return null;
+        } else {
+            BrowseNode nextChild = getEldestChild(mCurrentBrowseNode, target);
+            if (nextChild == null) {
+                return mNavigateUpNode;
+            } else {
+                return nextChild;
+            }
+        }
+    }
+
+    static BrowseNode getEldestChild(BrowseNode ancestor, BrowseNode target) {
+        // ancestor is an ancestor of target
+        BrowseNode descendant = target;
+        if (DBG) {
+            Log.d(TAG, "NAVIGATING ancestor" + ancestor.toString() + "Target"
+                    + target.toString());
+        }
+        while (!ancestor.equals(descendant.mParent)) {
+            descendant = descendant.mParent;
+            if (descendant == null) {
+                return null;
+            }
+        }
+        if (DBG) Log.d(TAG, "NAVIGATING Descendant" + descendant.toString());
+        return descendant;
     }
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
index c34a2d7..362548e 100644
--- a/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
+++ b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
@@ -16,12 +16,11 @@
 
 package com.android.bluetooth.avrcpcontroller;
 
-import android.bluetooth.BluetoothAvrcpPlayerSettings;
+import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
+import android.util.SparseArray;
 
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
 
 /*
  * Contains information Player Application Setting extended from BluetootAvrcpPlayerSettings
@@ -32,10 +31,10 @@
     /*
      * Values for SetPlayerApplicationSettings from AVRCP Spec V1.6 Appendix F.
      */
-    private static final byte JNI_ATTRIB_EQUALIZER_STATUS = 0x01;
-    private static final byte JNI_ATTRIB_REPEAT_STATUS = 0x02;
-    private static final byte JNI_ATTRIB_SHUFFLE_STATUS = 0x03;
-    private static final byte JNI_ATTRIB_SCAN_STATUS = 0x04;
+    static final byte EQUALIZER_STATUS = 0x01;
+    static final byte REPEAT_STATUS = 0x02;
+    static final byte SHUFFLE_STATUS = 0x03;
+    static final byte SCAN_STATUS = 0x04;
 
     private static final byte JNI_EQUALIZER_STATUS_OFF = 0x01;
     private static final byte JNI_EQUALIZER_STATUS_ON = 0x02;
@@ -55,18 +54,17 @@
 
     private static final byte JNI_STATUS_INVALID = -1;
 
-
     /*
      * Hash map of current settings.
      */
-    private Map<Integer, Integer> mSettings = new HashMap<Integer, Integer>();
+    private SparseArray<Integer> mSettings = new SparseArray<>();
 
     /*
      * Hash map of supported values, a setting should be supported by the remote in order to enable
      * in mSettings.
      */
-    private Map<Integer, ArrayList<Integer>> mSupportedValues =
-            new HashMap<Integer, ArrayList<Integer>>();
+    private SparseArray<ArrayList<Integer>> mSupportedValues =
+            new SparseArray<ArrayList<Integer>>();
 
     /* Convert from JNI array to Java classes. */
     static PlayerApplicationSettings makeSupportedSettings(byte[] btAvrcpAttributeList) {
@@ -82,8 +80,7 @@
                     supportedValues.add(
                             mapAttribIdValtoAvrcpPlayerSetting(attrId, btAvrcpAttributeList[i++]));
                 }
-                newObj.mSupportedValues.put(mapBTAttribIdToAvrcpPlayerSettings(attrId),
-                        supportedValues);
+                newObj.mSupportedValues.put(attrId, supportedValues);
             }
         } catch (ArrayIndexOutOfBoundsException exception) {
             Log.e(TAG, "makeSupportedSettings attributeList index error.");
@@ -91,25 +88,13 @@
         return newObj;
     }
 
-    public BluetoothAvrcpPlayerSettings getAvrcpSettings() {
-        int supportedSettings = 0;
-        for (Integer setting : mSettings.keySet()) {
-            supportedSettings |= setting;
-        }
-        BluetoothAvrcpPlayerSettings result = new BluetoothAvrcpPlayerSettings(supportedSettings);
-        for (Integer setting : mSettings.keySet()) {
-            result.addSettingValue(setting, mSettings.get(setting));
-        }
-        return result;
-    }
-
     static PlayerApplicationSettings makeSettings(byte[] btAvrcpAttributeList) {
         PlayerApplicationSettings newObj = new PlayerApplicationSettings();
         try {
             for (int i = 0; i < btAvrcpAttributeList.length; ) {
                 byte attrId = btAvrcpAttributeList[i++];
 
-                newObj.mSettings.put(mapBTAttribIdToAvrcpPlayerSettings(attrId),
+                newObj.mSettings.put(attrId,
                         mapAttribIdValtoAvrcpPlayerSetting(attrId, btAvrcpAttributeList[i++]));
             }
         } catch (ArrayIndexOutOfBoundsException exception) {
@@ -123,177 +108,69 @@
         mSupportedValues = updates.mSupportedValues;
     }
 
-    public void setValues(BluetoothAvrcpPlayerSettings updates) {
-        int supportedSettings = updates.getSettings();
-        for (int i = 1; i <= BluetoothAvrcpPlayerSettings.SETTING_SCAN; i++) {
-            if ((i & supportedSettings) > 0) {
-                mSettings.put(i, updates.getSettingValue(i));
-            }
-        }
+    public boolean supportsSetting(int settingType, int settingValue) {
+        if (null == mSupportedValues.get(settingType)) return false;
+        return mSupportedValues.valueAt(settingType).contains(settingValue);
     }
 
-    /*
-     * Check through all settings to ensure that they are all available to be set and then check
-     * that the desired value is in fact supported by our remote player.
-     */
-    public boolean supportsSettings(BluetoothAvrcpPlayerSettings settingsToCheck) {
-        int settingSubset = settingsToCheck.getSettings();
-        int supportedSettings = 0;
-        for (Integer setting : mSupportedValues.keySet()) {
-            supportedSettings |= setting;
-        }
-        try {
-            if ((supportedSettings & settingSubset) == settingSubset) {
-                for (Integer settingId : mSettings.keySet()) {
-                    // The setting is in both settings to check and supported settings but the
-                    // value is not supported.
-                    if ((settingId & settingSubset) == settingId && (!mSupportedValues.get(
-                            settingId).contains(settingsToCheck.getSettingValue(settingId)))) {
-                        return false;
-                    }
-                }
-                return true;
-            }
-        } catch (NullPointerException e) {
-            Log.e(TAG,
-                    "supportsSettings received a supported setting that has no supported values.");
-        }
-        return false;
+    public boolean supportsSetting(int settingType) {
+        return (null != mSupportedValues.get(settingType));
     }
 
-    // Convert currently desired settings into an attribute array to pass to the native layer to
-    // enable them.
-    public ArrayList<Byte> getNativeSettings() {
-        int i = 0;
-        ArrayList<Byte> attribArray = new ArrayList<Byte>();
-        for (Integer settingId : mSettings.keySet()) {
-            switch (settingId) {
-                case BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER:
-                    attribArray.add(JNI_ATTRIB_EQUALIZER_STATUS);
-                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId,
-                            mSettings.get(settingId)));
-                    break;
-                case BluetoothAvrcpPlayerSettings.SETTING_REPEAT:
-                    attribArray.add(JNI_ATTRIB_REPEAT_STATUS);
-                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId,
-                            mSettings.get(settingId)));
-                    break;
-                case BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE:
-                    attribArray.add(JNI_ATTRIB_SHUFFLE_STATUS);
-                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId,
-                            mSettings.get(settingId)));
-                    break;
-                case BluetoothAvrcpPlayerSettings.SETTING_SCAN:
-                    attribArray.add(JNI_ATTRIB_SCAN_STATUS);
-                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId,
-                            mSettings.get(settingId)));
-                    break;
-                default:
-                    Log.w(TAG, "Unknown setting found in getNativeSettings: " + settingId);
-            }
-        }
-        return attribArray;
+    public int getSetting(int settingType) {
+        if (null == mSettings.get(settingType)) return -1;
+        return mSettings.get(settingType);
     }
 
     // Convert a native Attribute Id/Value pair into the AVRCP equivalent value.
     private static int mapAttribIdValtoAvrcpPlayerSetting(byte attribId, byte attribVal) {
-        if (attribId == JNI_ATTRIB_EQUALIZER_STATUS) {
-            switch (attribVal) {
-                case JNI_EQUALIZER_STATUS_OFF:
-                    return BluetoothAvrcpPlayerSettings.STATE_OFF;
-                case JNI_EQUALIZER_STATUS_ON:
-                    return BluetoothAvrcpPlayerSettings.STATE_ON;
-            }
-        } else if (attribId == JNI_ATTRIB_REPEAT_STATUS) {
+        if (attribId == REPEAT_STATUS) {
             switch (attribVal) {
                 case JNI_REPEAT_STATUS_ALL_TRACK_REPEAT:
-                    return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+                    return PlaybackStateCompat.REPEAT_MODE_ALL;
                 case JNI_REPEAT_STATUS_GROUP_REPEAT:
-                    return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+                    return PlaybackStateCompat.REPEAT_MODE_GROUP;
                 case JNI_REPEAT_STATUS_OFF:
-                    return BluetoothAvrcpPlayerSettings.STATE_OFF;
+                    return PlaybackStateCompat.REPEAT_MODE_NONE;
                 case JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT:
-                    return BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK;
+                    return PlaybackStateCompat.REPEAT_MODE_ONE;
             }
-        } else if (attribId == JNI_ATTRIB_SCAN_STATUS) {
-            switch (attribVal) {
-                case JNI_SCAN_STATUS_ALL_TRACK_SCAN:
-                    return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
-                case JNI_SCAN_STATUS_GROUP_SCAN:
-                    return BluetoothAvrcpPlayerSettings.STATE_GROUP;
-                case JNI_SCAN_STATUS_OFF:
-                    return BluetoothAvrcpPlayerSettings.STATE_OFF;
-            }
-        } else if (attribId == JNI_ATTRIB_SHUFFLE_STATUS) {
+        } else if (attribId == SHUFFLE_STATUS) {
             switch (attribVal) {
                 case JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE:
-                    return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+                    return PlaybackStateCompat.SHUFFLE_MODE_ALL;
                 case JNI_SHUFFLE_STATUS_GROUP_SHUFFLE:
-                    return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+                    return PlaybackStateCompat.SHUFFLE_MODE_GROUP;
                 case JNI_SHUFFLE_STATUS_OFF:
-                    return BluetoothAvrcpPlayerSettings.STATE_OFF;
-            }
-        }
-        return BluetoothAvrcpPlayerSettings.STATE_INVALID;
-    }
-
-    // Convert an AVRCP Setting/Value pair into the native equivalent value;
-    private static byte mapAvrcpPlayerSettingstoBTattribVal(int mSetting, int mSettingVal) {
-        if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) {
-            switch (mSettingVal) {
-                case BluetoothAvrcpPlayerSettings.STATE_OFF:
-                    return JNI_EQUALIZER_STATUS_OFF;
-                case BluetoothAvrcpPlayerSettings.STATE_ON:
-                    return JNI_EQUALIZER_STATUS_ON;
-            }
-        } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_REPEAT) {
-            switch (mSettingVal) {
-                case BluetoothAvrcpPlayerSettings.STATE_OFF:
-                    return JNI_REPEAT_STATUS_OFF;
-                case BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK:
-                    return JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT;
-                case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
-                    return JNI_REPEAT_STATUS_ALL_TRACK_REPEAT;
-                case BluetoothAvrcpPlayerSettings.STATE_GROUP:
-                    return JNI_REPEAT_STATUS_GROUP_REPEAT;
-            }
-        } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) {
-            switch (mSettingVal) {
-                case BluetoothAvrcpPlayerSettings.STATE_OFF:
-                    return JNI_SHUFFLE_STATUS_OFF;
-                case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
-                    return JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE;
-                case BluetoothAvrcpPlayerSettings.STATE_GROUP:
-                    return JNI_SHUFFLE_STATUS_GROUP_SHUFFLE;
-            }
-        } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SCAN) {
-            switch (mSettingVal) {
-                case BluetoothAvrcpPlayerSettings.STATE_OFF:
-                    return JNI_SCAN_STATUS_OFF;
-                case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
-                    return JNI_SCAN_STATUS_ALL_TRACK_SCAN;
-                case BluetoothAvrcpPlayerSettings.STATE_GROUP:
-                    return JNI_SCAN_STATUS_GROUP_SCAN;
+                    return PlaybackStateCompat.SHUFFLE_MODE_NONE;
             }
         }
         return JNI_STATUS_INVALID;
     }
 
-    // convert a native Attribute Id into the AVRCP Setting equivalent value;
-    private static int mapBTAttribIdToAvrcpPlayerSettings(byte attribId) {
-        switch (attribId) {
-            case JNI_ATTRIB_EQUALIZER_STATUS:
-                return BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER;
-            case JNI_ATTRIB_REPEAT_STATUS:
-                return BluetoothAvrcpPlayerSettings.SETTING_REPEAT;
-            case JNI_ATTRIB_SHUFFLE_STATUS:
-                return BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE;
-            case JNI_ATTRIB_SCAN_STATUS:
-                return BluetoothAvrcpPlayerSettings.SETTING_SCAN;
-            default:
-                return BluetoothAvrcpPlayerSettings.STATE_INVALID;
+    // Convert an AVRCP Setting/Value pair into the native equivalent value;
+    static byte mapAvrcpPlayerSettingstoBTattribVal(int mSetting, int mSettingVal) {
+        if (mSetting == REPEAT_STATUS) {
+            switch (mSettingVal) {
+                case PlaybackStateCompat.REPEAT_MODE_NONE:
+                    return JNI_REPEAT_STATUS_OFF;
+                case PlaybackStateCompat.REPEAT_MODE_ONE:
+                    return JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT;
+                case PlaybackStateCompat.REPEAT_MODE_ALL:
+                    return JNI_REPEAT_STATUS_ALL_TRACK_REPEAT;
+                case PlaybackStateCompat.REPEAT_MODE_GROUP:
+                    return JNI_REPEAT_STATUS_GROUP_REPEAT;
+            }
+        } else if (mSetting == SHUFFLE_STATUS) {
+            switch (mSettingVal) {
+                case PlaybackStateCompat.SHUFFLE_MODE_NONE:
+                    return JNI_SHUFFLE_STATUS_OFF;
+                case PlaybackStateCompat.SHUFFLE_MODE_ALL:
+                    return JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE;
+                case PlaybackStateCompat.SHUFFLE_MODE_GROUP:
+                    return JNI_SHUFFLE_STATUS_GROUP_SHUFFLE;
+            }
         }
+        return JNI_STATUS_INVALID;
     }
-
 }
-
diff --git a/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java b/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
deleted file mode 100644
index 7946747..0000000
--- a/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.avrcpcontroller;
-
-import android.bluetooth.BluetoothDevice;
-
-import com.android.bluetooth.Utils;
-
-/*
- * Contains information about remote device specifically the player and features enabled on it along
- * with an encapsulation of the current track and playlist information.
- */
-class RemoteDevice {
-
-    /*
-     * Remote features from JNI
-     */
-    private static final int FEAT_NONE = 0;
-    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 VOLUME_LABEL_UNDEFINED = -1;
-
-    final BluetoothDevice mBTDevice;
-    private int mRemoteFeatures;
-    private boolean mAbsVolNotificationRequested;
-    private boolean mFirstAbsVolCmdRecvd;
-    private int mNotificationLabel;
-
-    RemoteDevice(BluetoothDevice mDevice) {
-        mBTDevice = mDevice;
-        mRemoteFeatures = FEAT_NONE;
-        mAbsVolNotificationRequested = false;
-        mNotificationLabel = VOLUME_LABEL_UNDEFINED;
-        mFirstAbsVolCmdRecvd = false;
-    }
-
-    synchronized void setRemoteFeatures(int remoteFeatures) {
-        mRemoteFeatures = remoteFeatures;
-    }
-
-    public synchronized byte[] getBluetoothAddress() {
-        return Utils.getByteAddress(mBTDevice);
-    }
-
-    public synchronized void setNotificationLabel(int label) {
-        mNotificationLabel = label;
-    }
-
-    public synchronized int getNotificationLabel() {
-        return mNotificationLabel;
-    }
-
-    public synchronized void setAbsVolNotificationRequested(boolean request) {
-        mAbsVolNotificationRequested = request;
-    }
-
-    public synchronized boolean getAbsVolNotificationRequested() {
-        return mAbsVolNotificationRequested;
-    }
-
-    public synchronized void setFirstAbsVolCmdRecvd() {
-        mFirstAbsVolCmdRecvd = true;
-    }
-
-    public synchronized boolean getFirstAbsVolCmdRecvd() {
-        return mFirstAbsVolCmdRecvd;
-    }
-}
diff --git a/src/com/android/bluetooth/avrcpcontroller/StackEvent.java b/src/com/android/bluetooth/avrcpcontroller/StackEvent.java
new file mode 100644
index 0000000..1f96bd2
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/StackEvent.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.avrcpcontroller;
+
+final class StackEvent {
+    // Event types for STACK_EVENT message
+    static final int EVENT_TYPE_NONE = 0;
+    static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+    static final int EVENT_TYPE_RC_FEATURES = 2;
+
+    int mType = EVENT_TYPE_NONE;
+    boolean mRemoteControlConnected;
+    boolean mBrowsingConnected;
+    int mFeatures;
+
+    private StackEvent(int type) {
+        this.mType = type;
+    }
+
+    @Override
+    public String toString() {
+        switch (mType) {
+            case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                return "EVENT_TYPE_CONNECTION_STATE_CHANGED " + mRemoteControlConnected;
+            case EVENT_TYPE_RC_FEATURES:
+                return "EVENT_TYPE_RC_FEATURES";
+            default:
+                return "Unknown";
+        }
+    }
+
+    static StackEvent connectionStateChanged(boolean remoteControlConnected,
+            boolean browsingConnected) {
+        StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.mRemoteControlConnected = remoteControlConnected;
+        event.mBrowsingConnected = browsingConnected;
+        return event;
+    }
+
+    static StackEvent rcFeatures(int features) {
+        StackEvent event = new StackEvent(EVENT_TYPE_RC_FEATURES);
+        event.mFeatures = features;
+        return event;
+    }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
index e89fb4c..fd1b784 100644
--- a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
+++ b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
@@ -14,34 +14,11 @@
  * limitations under the License.
  */
 
-
 package com.android.bluetooth.avrcpcontroller;
 
 import android.media.MediaMetadata;
-import android.util.Log;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/*
- * Contains information about tracks that either currently playing or maintained in playlist
- * This is used as a local repository for information that will be passed on as MediaMetadata to the
- * MediaSessionServicve
- */
-class TrackInfo {
-    private static final String TAG = "AvrcpTrackInfo";
-    private static final boolean VDBG = false;
-
-    /*
-     * Default values for each of the items from JNI
-     */
-    private static final int TRACK_NUM_INVALID = -1;
-    private static final int TOTAL_TRACKS_INVALID = -1;
-    private static final int TOTAL_TRACK_TIME_INVALID = -1;
-    private static final String UNPOPULATED_ATTRIBUTE = "";
-
+final class TrackInfo {
     /*
      *Element Id Values for GetMetaData  from JNI
      */
@@ -53,95 +30,49 @@
     private static final int MEDIA_ATTRIBUTE_GENRE = 0x06;
     private static final int MEDIA_ATTRIBUTE_PLAYING_TIME = 0x07;
 
-
-    private final String mArtistName;
-    private final String mTrackTitle;
-    private final String mAlbumTitle;
-    private final String mGenre;
-    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.
-
-    TrackInfo() {
-        this(new ArrayList<Integer>(), new ArrayList<String>());
-    }
-
-    TrackInfo(List<Integer> attrIds, List<String> attrMap) {
-        Map<Integer, String> attributeMap = new HashMap<>();
-        for (int i = 0; i < attrIds.size(); i++) {
-            attributeMap.put(attrIds.get(i), attrMap.get(i));
+    static MediaMetadata getMetadata(int[] attrIds, String[] attrMap) {
+        MediaMetadata.Builder metaDataBuilder = new MediaMetadata.Builder();
+        int attributeCount = Math.max(attrIds.length, attrMap.length);
+        for (int i = 0; i < attributeCount; i++) {
+            switch (attrIds[i]) {
+                case MEDIA_ATTRIBUTE_TITLE:
+                    metaDataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, attrMap[i]);
+                    break;
+                case MEDIA_ATTRIBUTE_ARTIST_NAME:
+                    metaDataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, attrMap[i]);
+                    break;
+                case MEDIA_ATTRIBUTE_ALBUM_NAME:
+                    metaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM, attrMap[i]);
+                    break;
+                case MEDIA_ATTRIBUTE_TRACK_NUMBER:
+                    try {
+                        metaDataBuilder.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER,
+                                Long.valueOf(attrMap[i]));
+                    } catch (java.lang.NumberFormatException e) {
+                        // If Track Number doesn't parse, leave it unset
+                    }
+                    break;
+                case MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER:
+                    try {
+                        metaDataBuilder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS,
+                                Long.valueOf(attrMap[i]));
+                    } catch (java.lang.NumberFormatException e) {
+                        // If Total Track Number doesn't parse, leave it unset
+                    }
+                    break;
+                case MEDIA_ATTRIBUTE_GENRE:
+                    metaDataBuilder.putString(MediaMetadata.METADATA_KEY_GENRE, attrMap[i]);
+                    break;
+                case MEDIA_ATTRIBUTE_PLAYING_TIME:
+                    try {
+                        metaDataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION,
+                                Long.valueOf(attrMap[i]));
+                    } catch (java.lang.NumberFormatException e) {
+                        // If Playing Time doesn't parse, leave it unset
+                    }
+                    break;
+            }
         }
-
-        String attribute;
-        mTrackTitle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_TITLE, UNPOPULATED_ATTRIBUTE);
-
-        mArtistName = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_ARTIST_NAME, UNPOPULATED_ATTRIBUTE);
-
-        mAlbumTitle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_ALBUM_NAME, UNPOPULATED_ATTRIBUTE);
-
-        attribute = attributeMap.get(MEDIA_ATTRIBUTE_TRACK_NUMBER);
-        mTrackNum = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute)
-                : TRACK_NUM_INVALID;
-
-        attribute = attributeMap.get(MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER);
-        mTotalTracks = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute)
-                : TOTAL_TRACKS_INVALID;
-
-        mGenre = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_GENRE, UNPOPULATED_ATTRIBUTE);
-
-        attribute = attributeMap.get(MEDIA_ATTRIBUTE_PLAYING_TIME);
-        mTrackLen = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute)
-                : TOTAL_TRACK_TIME_INVALID;
-    }
-
-    @Override
-    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) + "]";
-    }
-
-    public MediaMetadata getMediaMetaData() {
-        if (VDBG) {
-            Log.d(TAG, " TrackInfo " + toString());
-        }
-        MediaMetadata.Builder mMetaDataBuilder = new MediaMetadata.Builder();
-        mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, mArtistName);
-        mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, mTrackTitle);
-        mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM, mAlbumTitle);
-        mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_GENRE, mGenre);
-        mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, mTrackNum);
-        mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, mTotalTracks);
-        mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION, mTrackLen);
-        return mMetaDataBuilder.build();
-    }
-
-
-    public String displayMetaData() {
-        MediaMetadata metaData = getMediaMetaData();
-        StringBuffer sb = new StringBuffer();
-        /* getDescription only contains artist, title and album */
-        sb.append(metaData.getDescription().toString() + " ");
-        if (metaData.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
-            sb.append(metaData.getString(MediaMetadata.METADATA_KEY_GENRE) + " ");
-        }
-        if (metaData.containsKey(MediaMetadata.METADATA_KEY_MEDIA_ID)) {
-            sb.append(metaData.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) + " ");
-        }
-        if (metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
-            sb.append(
-                    Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) + " ");
-        }
-        if (metaData.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
-            sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS)) + " ");
-        }
-        if (metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
-            sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
-        }
-        if (metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
-            sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
-        }
-        return sb.toString();
+        return metaDataBuilder.build();
     }
 }
diff --git a/src/com/android/bluetooth/btservice/AbstractionLayer.java b/src/com/android/bluetooth/btservice/AbstractionLayer.java
index 6a2e636..e15104d 100644
--- a/src/com/android/bluetooth/btservice/AbstractionLayer.java
+++ b/src/com/android/bluetooth/btservice/AbstractionLayer.java
@@ -52,6 +52,9 @@
     public static final int BT_DEVICE_TYPE_BLE = 0x02;
     public static final int BT_DEVICE_TYPE_DUAL = 0x03;
 
+    static final int BT_PROPERTY_LOCAL_IO_CAPS = 0x0e;
+    static final int BT_PROPERTY_LOCAL_IO_CAPS_BLE = 0x0f;
+
     static final int BT_BOND_STATE_NONE = 0x00;
     static final int BT_BOND_STATE_BONDING = 0x01;
     static final int BT_BOND_STATE_BONDED = 0x02;
diff --git a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
index e1f999d..69381c5 100644
--- a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -33,12 +33,12 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.hearingaid.HearingAidService;
 import com.android.bluetooth.hfp.HeadsetService;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.LinkedList;
 import java.util.List;
diff --git a/src/com/android/bluetooth/btservice/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java
index 981a45a..021f0a9 100644
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -24,6 +24,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothHidDevice;
 import android.bluetooth.BluetoothHidHost;
 import android.bluetooth.BluetoothMap;
@@ -41,11 +42,12 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings.Secure;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 import android.util.Pair;
 import android.util.StatsLog;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
 
@@ -77,6 +79,9 @@
     private volatile int mScanMode;
     private volatile int mDiscoverableTimeout;
     private volatile ParcelUuid[] mUuids;
+    private volatile int mLocalIOCapability = BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+    private volatile int mLocalIOCapabilityBLE = BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+
     private CopyOnWriteArrayList<BluetoothDevice> mBondedDevices =
             new CopyOnWriteArrayList<BluetoothDevice>();
 
@@ -130,6 +135,9 @@
                 case BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED:
                     sendConnectionStateChange(BluetoothProfile.HEADSET_CLIENT, intent);
                     break;
+                case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
+                    sendConnectionStateChange(BluetoothProfile.HEARING_AID, intent);
+                    break;
                 case BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED:
                     sendConnectionStateChange(BluetoothProfile.A2DP_SINK, intent);
                     break;
@@ -203,6 +211,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED);
@@ -287,6 +296,45 @@
         }
     }
 
+    boolean setIoCapability(int capability) {
+        synchronized (mObject) {
+            boolean result = mService.setAdapterPropertyNative(
+                    AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS, Utils.intToByteArray(capability));
+
+            if (result) {
+                mLocalIOCapability = capability;
+            }
+
+            return result;
+        }
+    }
+
+    int getIoCapability() {
+        synchronized (mObject) {
+            return mLocalIOCapability;
+        }
+    }
+
+    boolean setLeIoCapability(int capability) {
+        synchronized (mObject) {
+            boolean result = mService.setAdapterPropertyNative(
+                    AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS_BLE,
+                    Utils.intToByteArray(capability));
+
+            if (result) {
+                mLocalIOCapabilityBLE = capability;
+            }
+
+            return result;
+        }
+    }
+
+    int getLeIoCapability() {
+        synchronized (mObject) {
+            return mLocalIOCapabilityBLE;
+        }
+    }
+
     /**
      * @return the mScanMode
      */
@@ -533,11 +581,9 @@
         Log.d(TAG,
                 "PROFILE_CONNECTION_STATE_CHANGE: profile=" + profile + ", device=" + device + ", "
                         + prevState + " -> " + state);
-        String ssaid = Secure.getString(mService.getContentResolver(), Secure.ANDROID_ID);
-        String combined = ssaid + device.getAddress();
-        int obfuscated_id = combined.hashCode() & 0xFFFF; // Last two bytes only
-        StatsLog.write(StatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED,
-                state, obfuscated_id, profile);
+        StatsLog.write(StatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED, state, 0 /* deprecated */,
+                profile, mService.obfuscateAddress(device));
+
         if (!isNormalStateTransition(prevState, state)) {
             Log.w(TAG,
                     "PROFILE_CONNECTION_STATE_CHANGE: unexpected transition for profile=" + profile
@@ -794,6 +840,16 @@
                         updateFeatureSupport(val);
                         break;
 
+                    case AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS:
+                        mLocalIOCapability = Utils.byteArrayToInt(val);
+                        debugLog("mLocalIOCapability set to " + mLocalIOCapability);
+                        break;
+
+                    case AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS_BLE:
+                        mLocalIOCapabilityBLE = Utils.byteArrayToInt(val);
+                        debugLog("mLocalIOCapabilityBLE set to " + mLocalIOCapabilityBLE);
+                        break;
+
                     default:
                         errorLog("Property change not handled in Java land:" + type);
                 }
@@ -876,6 +932,7 @@
             Intent intent;
             if (state == AbstractionLayer.BT_DISCOVERY_STOPPED) {
                 mDiscovering = false;
+                mService.clearDiscoveringPackages();
                 mDiscoveryEndMs = System.currentTimeMillis();
                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
                 mService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 9d8cde9..c671efd 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager;
 import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.app.Service;
 import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -25,8 +26,10 @@
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProtoEnums;
 import android.bluetooth.IBluetooth;
 import android.bluetooth.IBluetoothCallback;
+import android.bluetooth.IBluetoothMetadataListener;
 import android.bluetooth.IBluetoothSocketManager;
 import android.bluetooth.OobData;
 import android.bluetooth.UidTraffic;
@@ -66,6 +69,8 @@
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.bluetooth.btservice.storage.MetadataDatabase;
 import com.android.bluetooth.gatt.GattService;
 import com.android.bluetooth.sdp.SdpManager;
 import com.android.internal.R;
@@ -129,6 +134,8 @@
 
     private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
 
+    private final ArrayList<DiscoveringPackage> mDiscoveringPackages = new ArrayList<>();
+
     static {
         classInitNative();
     }
@@ -165,6 +172,8 @@
 
     private boolean mNativeAvailable;
     private boolean mCleaningUp;
+    private final HashMap<BluetoothDevice, ArrayList<IBluetoothMetadataListener>>
+            mMetadataListeners = new HashMap<>();
     private final HashMap<String, Integer> mProfileServicesState = new HashMap<String, Integer>();
     //Only BluetoothManagerService should be registered
     private RemoteCallbackList<IBluetoothCallback> mCallbacks;
@@ -182,6 +191,9 @@
     private ProfileObserver mProfileObserver;
     private PhonePolicy mPhonePolicy;
     private ActiveDeviceManager mActiveDeviceManager;
+    private DatabaseManager mDatabaseManager;
+    private SilenceDeviceManager mSilenceDeviceManager;
+    private AppOpsManager mAppOps;
 
     /**
      * Register a {@link ProfileService} with AdapterService.
@@ -224,19 +236,19 @@
     class AdapterServiceHandler extends Handler {
         @Override
         public void handleMessage(Message msg) {
-            debugLog("handleMessage() - Message: " + msg.what);
+            verboseLog("handleMessage() - Message: " + msg.what);
 
             switch (msg.what) {
                 case MESSAGE_PROFILE_SERVICE_STATE_CHANGED:
-                    debugLog("handleMessage() - MESSAGE_PROFILE_SERVICE_STATE_CHANGED");
+                    verboseLog("handleMessage() - MESSAGE_PROFILE_SERVICE_STATE_CHANGED");
                     processProfileServiceStateChanged((ProfileService) msg.obj, msg.arg1);
                     break;
                 case MESSAGE_PROFILE_SERVICE_REGISTERED:
-                    debugLog("handleMessage() - MESSAGE_PROFILE_SERVICE_REGISTERED");
+                    verboseLog("handleMessage() - MESSAGE_PROFILE_SERVICE_REGISTERED");
                     registerProfileService((ProfileService) msg.obj);
                     break;
                 case MESSAGE_PROFILE_SERVICE_UNREGISTERED:
-                    debugLog("handleMessage() - MESSAGE_PROFILE_SERVICE_UNREGISTERED");
+                    verboseLog("handleMessage() - MESSAGE_PROFILE_SERVICE_UNREGISTERED");
                     unregisterProfileService((ProfileService) msg.obj);
                     break;
             }
@@ -252,7 +264,7 @@
 
         private void unregisterProfileService(ProfileService profile) {
             if (!mRegisteredProfiles.contains(profile)) {
-                Log.e(TAG, profile.getName() + " not registered (UNREGISTERED).");
+                Log.e(TAG, profile.getName() + " not registered (UNREGISTER).");
                 return;
             }
             mRegisteredProfiles.remove(profile);
@@ -277,6 +289,8 @@
                         mAdapterProperties.onBluetoothReady();
                         updateUuids();
                         setBluetoothClassFromConfig();
+                        getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS);
+                        getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS_BLE);
                         mAdapterStateMachine.sendMessage(AdapterState.BREDR_STARTED);
                     }
                     break;
@@ -296,7 +310,6 @@
                         mAdapterStateMachine.sendMessage(AdapterState.BREDR_STOPPED);
                     } else if (mRunningProfiles.size() == 0) {
                         disableNative();
-                        mAdapterStateMachine.sendMessage(AdapterState.BLE_STOPPED);
                     }
                     break;
                 default:
@@ -372,6 +385,7 @@
         debugLog("onCreate()");
         mRemoteDevices = new RemoteDevices(this, Looper.getMainLooper());
         mRemoteDevices.init();
+        clearDiscoveringPackages();
         mBinder = new AdapterServiceBinder(this);
         mAdapterProperties = new AdapterProperties(this);
         mAdapterStateMachine = AdapterState.make(this);
@@ -379,6 +393,7 @@
         initNative();
         mNativeAvailable = true;
         mCallbacks = new RemoteCallbackList<IBluetoothCallback>();
+        mAppOps = getSystemService(AppOpsManager.class);
         //Load the name and address
         getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDADDR);
         getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDNAME);
@@ -407,6 +422,13 @@
         mActiveDeviceManager = new ActiveDeviceManager(this, new ServiceFactory());
         mActiveDeviceManager.start();
 
+        mDatabaseManager = new DatabaseManager(this);
+        mDatabaseManager.start(MetadataDatabase.createDatabase(this));
+
+        mSilenceDeviceManager = new SilenceDeviceManager(this, new ServiceFactory(),
+                Looper.getMainLooper());
+        mSilenceDeviceManager.start();
+
         setAdapterService(this);
 
         // First call to getSharedPreferences will result in a file read into
@@ -522,6 +544,7 @@
     void stateChangeCallback(int status) {
         if (status == AbstractionLayer.BT_STATE_OFF) {
             debugLog("stateChangeCallback: disableNative() completed");
+            mAdapterStateMachine.sendMessage(AdapterState.BLE_STOPPED);
         } else if (status == AbstractionLayer.BT_STATE_ON) {
             mAdapterStateMachine.sendMessage(AdapterState.BLE_STARTED);
         } else {
@@ -648,6 +671,10 @@
             }
         }
 
+        if (mDatabaseManager != null) {
+            mDatabaseManager.cleanup();
+        }
+
         if (mAdapterStateMachine != null) {
             mAdapterStateMachine.doQuit();
         }
@@ -683,6 +710,10 @@
             mPhonePolicy.cleanup();
         }
 
+        if (mSilenceDeviceManager != null) {
+            mSilenceDeviceManager.cleanup();
+        }
+
         if (mActiveDeviceManager != null) {
             mActiveDeviceManager.cleanup();
         }
@@ -873,6 +904,7 @@
             return service.setName(name);
         }
 
+        @Override
         public BluetoothClass getBluetoothClass() {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "getBluetoothClass() - Not allowed for non-active user");
@@ -884,6 +916,7 @@
             return service.getBluetoothClass();
         }
 
+        @Override
         public boolean setBluetoothClass(BluetoothClass bluetoothClass) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
@@ -898,6 +931,54 @@
         }
 
         @Override
+        public int getIoCapability() {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+                return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+            }
+
+            AdapterService service = getService();
+            if (service == null) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+            return service.getIoCapability();
+        }
+
+        @Override
+        public boolean setIoCapability(int capability) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+                return false;
+            }
+
+            AdapterService service = getService();
+            if (service == null) return false;
+            return service.setIoCapability(capability);
+        }
+
+        @Override
+        public int getLeIoCapability() {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+                return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+            }
+
+            AdapterService service = getService();
+            if (service == null) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+            return service.getLeIoCapability();
+        }
+
+        @Override
+        public boolean setLeIoCapability(int capability) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+                return false;
+            }
+
+            AdapterService service = getService();
+            if (service == null) return false;
+            return service.setLeIoCapability(capability);
+        }
+
+        @Override
         public int getScanMode() {
             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                 Log.w(TAG, "getScanMode() - Not allowed for non-active user");
@@ -954,7 +1035,7 @@
         }
 
         @Override
-        public boolean startDiscovery() {
+        public boolean startDiscovery(String callingPackage) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "startDiscovery() - Not allowed for non-active user");
                 return false;
@@ -964,7 +1045,7 @@
             if (service == null) {
                 return false;
             }
-            return service.startDiscovery();
+            return service.startDiscovery(callingPackage);
         }
 
         @Override
@@ -1293,6 +1374,34 @@
         }
 
         @Override
+        public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "setSilenceMode() - Not allowed for non-active user");
+                return false;
+            }
+
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.setSilenceMode(device, silence);
+        }
+
+        @Override
+        public boolean getSilenceMode(BluetoothDevice device) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "getSilenceMode() - Not allowed for non-active user");
+                return false;
+            }
+
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.getSilenceMode(device);
+        }
+
+        @Override
         public boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "setPhonebookAccessPermission() - Not allowed for non-active user");
@@ -1551,6 +1660,43 @@
         }
 
         @Override
+        public boolean registerMetadataListener(IBluetoothMetadataListener listener,
+                BluetoothDevice device) {
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.registerMetadataListener(listener, device);
+        }
+
+        @Override
+        public boolean unregisterMetadataListener(BluetoothDevice device) {
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.unregisterMetadataListener(device);
+        }
+
+        @Override
+        public boolean setMetadata(BluetoothDevice device, int key, byte[] value) {
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.setMetadata(device, key, value);
+        }
+
+        @Override
+        public byte[] getMetadata(BluetoothDevice device, int key) {
+            AdapterService service = getService();
+            if (service == null) {
+                return null;
+            }
+            return service.getMetadata(device, key);
+        }
+
+        @Override
         public void requestActivityInfo(ResultReceiver result) {
             Bundle bundle = new Bundle();
             bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, reportActivityInfo());
@@ -1690,6 +1836,47 @@
         return result && storeBluetoothClassConfig(bluetoothClass.getClassOfDevice());
     }
 
+    private boolean validateInputOutputCapability(int capability) {
+        if (capability < 0 || capability >= BluetoothAdapter.IO_CAPABILITY_MAX) {
+            Log.e(TAG, "Invalid IO capability value - " + capability);
+            return false;
+        }
+
+        return true;
+    }
+
+    int getIoCapability() {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+
+        return mAdapterProperties.getIoCapability();
+    }
+
+    boolean setIoCapability(int capability) {
+        enforceCallingOrSelfPermission(
+                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission");
+        if (!validateInputOutputCapability(capability)) {
+            return false;
+        }
+
+        return mAdapterProperties.setIoCapability(capability);
+    }
+
+    int getLeIoCapability() {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+
+        return mAdapterProperties.getLeIoCapability();
+    }
+
+    boolean setLeIoCapability(int capability) {
+        enforceCallingOrSelfPermission(
+                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission");
+        if (!validateInputOutputCapability(capability)) {
+            return false;
+        }
+
+        return mAdapterProperties.setLeIoCapability(capability);
+    }
+
     int getScanMode() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
@@ -1717,10 +1904,42 @@
         return mAdapterProperties.setDiscoverableTimeout(timeout);
     }
 
-    boolean startDiscovery() {
+    ArrayList<DiscoveringPackage> getDiscoveringPackages() {
+        return mDiscoveringPackages;
+    }
+
+    void clearDiscoveringPackages() {
+        synchronized (mDiscoveringPackages) {
+            mDiscoveringPackages.clear();
+        }
+    }
+
+    boolean startDiscovery(String callingPackage) {
+        UserHandle callingUser = UserHandle.of(UserHandle.getCallingUserId());
         debugLog("startDiscovery");
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+        boolean isQApp = Utils.isQApp(this, callingPackage);
+        String permission = null;
+        if (Utils.checkCallerHasNetworkSettingsPermission(this)) {
+            permission = android.Manifest.permission.NETWORK_SETTINGS;
+        } else if (Utils.checkCallerHasNetworkSetupWizardPermission(this)) {
+            permission = android.Manifest.permission.NETWORK_SETUP_WIZARD;
+        } else if (isQApp) {
+            if (!Utils.checkCallerHasFineLocation(this, mAppOps, callingPackage, callingUser)) {
+                return false;
+            }
+            permission = android.Manifest.permission.ACCESS_FINE_LOCATION;
+        } else {
+            if (!Utils.checkCallerHasCoarseLocation(this, mAppOps, callingPackage, callingUser)) {
+                return false;
+            }
+            permission = android.Manifest.permission.ACCESS_COARSE_LOCATION;
+        }
 
+        synchronized (mDiscoveringPackages) {
+            mDiscoveringPackages.add(new DiscoveringPackage(callingPackage, permission));
+        }
         return startDiscoveryNative();
     }
 
@@ -1753,6 +1972,16 @@
         return mAdapterProperties.getBondedDevices();
     }
 
+    /**
+     * Get the database manager to access Bluetooth storage
+     *
+     * @return {@link DatabaseManager} or null on error
+     */
+    @VisibleForTesting
+    public DatabaseManager getDatabase() {
+        return mDatabaseManager;
+    }
+
     int getAdapterConnectionState() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mAdapterProperties.getConnectionState();
@@ -1989,6 +2218,11 @@
             return false;
         }
 
+        StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+                obfuscateAddress(device), 0, device.getType(),
+                BluetoothDevice.BOND_BONDING,
+                BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_PIN_REPLIED,
+                accept ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
         byte[] addr = Utils.getBytesFromAddress(device.getAddress());
         return pinReplyNative(addr, accept, len, pinCode);
     }
@@ -2000,6 +2234,11 @@
             return false;
         }
 
+        StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+                obfuscateAddress(device), 0, device.getType(),
+                BluetoothDevice.BOND_BONDING,
+                BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REPLIED,
+                accept ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
         byte[] addr = Utils.getBytesFromAddress(device.getAddress());
         return sspReplyNative(addr, AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY, accept,
                 Utils.byteArrayToInt(passkey));
@@ -2013,6 +2252,11 @@
             return false;
         }
 
+        StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+                obfuscateAddress(device), 0, device.getType(),
+                BluetoothDevice.BOND_BONDING,
+                BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REPLIED,
+                accept ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
         byte[] addr = Utils.getBytesFromAddress(device.getAddress());
         return sspReplyNative(addr, AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION, accept,
                 0);
@@ -2029,9 +2273,20 @@
                 : BluetoothDevice.ACCESS_REJECTED;
     }
 
-    boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
+    boolean setSilenceMode(BluetoothDevice device, boolean silence) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH PRIVILEGED permission");
+        mSilenceDeviceManager.setSilenceMode(device, silence);
+        return true;
+    }
+
+    boolean getSilenceMode(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH PRIVILEGED permission");
+        return mSilenceDeviceManager.getSilenceMode(device);
+    }
+
+    boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
         SharedPreferences pref = getSharedPreferences(PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE,
                 Context.MODE_PRIVATE);
         SharedPreferences.Editor editor = pref.edit();
@@ -2118,6 +2373,9 @@
 
     boolean factoryReset() {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH permission");
+        if (mDatabaseManager != null) {
+            mDatabaseManager.factoryReset();
+        }
         return factoryResetNative();
     }
 
@@ -2417,6 +2675,64 @@
                 + ctrlState + "traffic = " + Arrays.toString(data));
     }
 
+    boolean registerMetadataListener(IBluetoothMetadataListener listener,
+            BluetoothDevice device) {
+        if (mMetadataListeners == null) {
+            return false;
+        }
+
+        ArrayList<IBluetoothMetadataListener> list = mMetadataListeners.get(device);
+        if (list == null) {
+            list = new ArrayList<>();
+        } else if (list.contains(listener)) {
+            // The device is already registered with this listener
+            return true;
+        }
+        list.add(listener);
+        mMetadataListeners.put(device, list);
+        return true;
+    }
+
+    boolean unregisterMetadataListener(BluetoothDevice device) {
+        if (mMetadataListeners == null) {
+            return false;
+        }
+        if (mMetadataListeners.containsKey(device)) {
+            mMetadataListeners.remove(device);
+        }
+        return true;
+    }
+
+    boolean setMetadata(BluetoothDevice device, int key, byte[] value) {
+        if (value.length > BluetoothDevice.METADATA_MAX_LENGTH) {
+            Log.e(TAG, "setMetadata: value length too long " + value.length);
+            return false;
+        }
+        return mDatabaseManager.setCustomMeta(device, key, value);
+    }
+
+    byte[] getMetadata(BluetoothDevice device, int key) {
+        return mDatabaseManager.getCustomMeta(device, key);
+    }
+
+    /**
+     * Update metadata change to registered listeners
+     */
+    @VisibleForTesting
+    public void metadataChanged(String address, int key, byte[] value) {
+        BluetoothDevice device = mRemoteDevices.getDevice(Utils.getBytesFromAddress(address));
+        if (mMetadataListeners.containsKey(device)) {
+            ArrayList<IBluetoothMetadataListener> list = mMetadataListeners.get(device);
+            for (IBluetoothMetadataListener listener : list) {
+                try {
+                    listener.onMetadataChanged(device, key, value);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "RemoteException when onMetadataChanged");
+                }
+            }
+        }
+    }
+
     private int getIdleCurrentMa() {
         return getResources().getInteger(R.integer.config_bluetooth_idle_cur_ma);
     }
@@ -2460,6 +2776,7 @@
         for (ProfileService profile : mRegisteredProfiles) {
             profile.dump(sb);
         }
+        mSilenceDeviceManager.dump(fd, writer, args);
 
         writer.write(sb.toString());
         writer.flush();
@@ -2527,6 +2844,20 @@
         }
     }
 
+    /**
+     *  Obfuscate Bluetooth MAC address into a PII free ID string
+     *
+     *  @param device Bluetooth device whose MAC address will be obfuscated
+     *  @return a byte array that is unique to this MAC address on this device,
+     *          or empty byte array when either device is null or obfuscateAddressNative fails
+     */
+    public byte[] obfuscateAddress(BluetoothDevice device) {
+        if (device == null) {
+            return new byte[0];
+        }
+        return obfuscateAddressNative(Utils.getByteAddress(device));
+    }
+
     static native void classInitNative();
 
     native boolean initNative();
@@ -2610,6 +2941,8 @@
 
     private native void interopDatabaseAddNative(int feature, byte[] address, int length);
 
+    private native byte[] obfuscateAddressNative(byte[] address);
+
     // Returns if this is a mock object. This is currently used in testing so that we may not call
     // System.exit() while finalizing the object. Otherwise GC of mock objects unfortunately ends up
     // calling finalize() which in turn calls System.exit() and the process crashes.
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index 81e5aae..3fa0d31 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -20,11 +20,13 @@
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProtoEnums;
 import android.bluetooth.OobData;
 import android.content.Intent;
 import android.os.Message;
 import android.os.UserHandle;
 import android.util.Log;
+import android.util.StatsLog;
 
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.a2dp.A2dpService;
@@ -39,6 +41,7 @@
 import com.android.internal.util.StateMachine;
 
 import java.util.ArrayList;
+import java.util.Objects;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -204,6 +207,11 @@
                 case BONDING_STATE_CHANGE:
                     int newState = msg.arg1;
                     int reason = getUnbondReasonFromHALCode(msg.arg2);
+                    // Bond is explicitly removed if we are in pending command state
+                    if (newState == BluetoothDevice.BOND_NONE
+                            && reason == BluetoothDevice.BOND_SUCCESS) {
+                        reason = BluetoothDevice.UNBOND_REASON_REMOVED;
+                    }
                     sendIntent(dev, newState, reason);
                     if (newState != BluetoothDevice.BOND_BONDING) {
                         /* this is either none/bonded, remove and transition */
@@ -315,8 +323,18 @@
             } else {
                 result = mAdapterService.createBondNative(addr, transport);
             }
-
+            StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+                    mAdapterService.obfuscateAddress(dev), transport, dev.getType(),
+                    BluetoothDevice.BOND_BONDING,
+                    oobData == null ? BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN
+                            : BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_OOB_DATA_PROVIDED,
+                    BluetoothProtoEnums.UNBOND_REASON_UNKNOWN);
             if (!result) {
+                StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+                        mAdapterService.obfuscateAddress(dev), transport, dev.getType(),
+                        BluetoothDevice.BOND_NONE, BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN,
+                        BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS);
+                // Using UNBOND_REASON_REMOVED for legacy reason
                 sendIntent(dev, BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED);
                 return false;
             } else if (transition) {
@@ -337,7 +355,7 @@
         intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         // 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);
+        mAdapterService.sendOrderedBroadcast(intent, AdapterService.BLUETOOTH_ADMIN_PERM);
     }
 
     @VisibleForTesting
@@ -368,7 +386,9 @@
         if (oldState == newState) {
             return;
         }
-
+        StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+                mAdapterService.obfuscateAddress(device), 0, device.getType(),
+                newState, BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN, reason);
         mAdapterProperties.onBondStateChanged(device, newState);
 
         if (devProp != null && ((devProp.getDeviceType() == BluetoothDevice.DEVICE_TYPE_CLASSIC
@@ -464,9 +484,14 @@
         if (device == null) {
             warnLog("Device is not known for:" + Utils.getAddressStringFromByte(address));
             mRemoteDevices.addDeviceProperties(address);
-            device = mRemoteDevices.getDevice(address);
+            device = Objects.requireNonNull(mRemoteDevices.getDevice(address));
         }
 
+        StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+                mAdapterService.obfuscateAddress(device), 0, device.getType(),
+                BluetoothDevice.BOND_BONDING,
+                BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REQUESTED, 0);
+
         Message msg = obtainMessage(SSP_REQUEST);
         msg.obj = device;
         if (displayPasskey) {
@@ -482,7 +507,14 @@
         BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
         if (bdDevice == null) {
             mRemoteDevices.addDeviceProperties(address);
+            bdDevice = Objects.requireNonNull(mRemoteDevices.getDevice(address));
         }
+
+        StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+                mAdapterService.obfuscateAddress(bdDevice), 0, bdDevice.getType(),
+                BluetoothDevice.BOND_BONDING,
+                BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_PIN_REQUESTED, 0);
+
         infoLog("pinRequestCallback: " + address + " name:" + name + " cod:" + cod);
 
         Message msg = obtainMessage(PIN_REQUEST);
@@ -518,11 +550,6 @@
         if (pbapClientService != null) {
             pbapClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
         }
-
-        // Clear Absolute Volume black list
-        if (a2dpService != null) {
-            a2dpService.resetAvrcpBlacklist(device);
-        }
     }
 
     private String state2str(int state) {
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index 8a9c0a1..2b1f46c 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -30,7 +30,6 @@
 import com.android.bluetooth.avrcp.AvrcpTargetService;
 import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
 import com.android.bluetooth.gatt.GattService;
-import com.android.bluetooth.hdp.HealthService;
 import com.android.bluetooth.hearingaid.HearingAidService;
 import com.android.bluetooth.hfp.HeadsetService;
 import com.android.bluetooth.hfpclient.HeadsetClientService;
@@ -73,8 +72,6 @@
                     (1 << BluetoothProfile.A2DP_SINK)),
             new ProfileConfig(HidHostService.class, R.bool.profile_supported_hid_host,
                     (1 << BluetoothProfile.HID_HOST)),
-            new ProfileConfig(HealthService.class, R.bool.profile_supported_hdp,
-                    (1 << BluetoothProfile.HEALTH)),
             new ProfileConfig(PanService.class, R.bool.profile_supported_pan,
                     (1 << BluetoothProfile.PAN)),
             new ProfileConfig(GattService.class, R.bool.profile_supported_gatt,
@@ -100,7 +97,8 @@
                     (1 << BluetoothProfile.OPP)),
             new ProfileConfig(BluetoothPbapService.class, R.bool.profile_supported_pbap,
                     (1 << BluetoothProfile.PBAP)),
-            new ProfileConfig(HearingAidService.class, R.bool.profile_supported_hearing_aid,
+            new ProfileConfig(HearingAidService.class,
+                    com.android.internal.R.bool.config_hearing_aid_profile_supported,
                     (1 << BluetoothProfile.HEARING_AID))
     };
 
diff --git a/src/com/android/bluetooth/btservice/DiscoveringPackage.java b/src/com/android/bluetooth/btservice/DiscoveringPackage.java
new file mode 100644
index 0000000..baa2b24
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/DiscoveringPackage.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice;
+
+final class DiscoveringPackage {
+    private String mPackageName;
+    private String mPermission;
+
+    DiscoveringPackage(String packageName, String permission) {
+        mPackageName = packageName;
+        mPermission = permission;
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public String getPermission() {
+        return mPermission;
+    }
+}
diff --git a/src/com/android/bluetooth/btservice/PhonePolicy.java b/src/com/android/bluetooth/btservice/PhonePolicy.java
index a0ad490..39b1252 100644
--- a/src/com/android/bluetooth/btservice/PhonePolicy.java
+++ b/src/com/android/bluetooth/btservice/PhonePolicy.java
@@ -31,7 +31,6 @@
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.Parcelable;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import com.android.bluetooth.a2dp.A2dpService;
@@ -40,6 +39,7 @@
 import com.android.bluetooth.hid.HidHostService;
 import com.android.bluetooth.pan.PanService;
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.HashSet;
 import java.util.List;
@@ -284,20 +284,23 @@
                 }
                 connectOtherProfile(device);
             }
-            if (prevState == BluetoothProfile.STATE_CONNECTING
-                    && nextState == BluetoothProfile.STATE_DISCONNECTED) {
-                HeadsetService hsService = mFactory.getHeadsetService();
-                boolean hsDisconnected = hsService == null || hsService.getConnectionState(device)
-                        == BluetoothProfile.STATE_DISCONNECTED;
-                A2dpService a2dpService = mFactory.getA2dpService();
-                boolean a2dpDisconnected = a2dpService == null
-                        || a2dpService.getConnectionState(device)
-                        == BluetoothProfile.STATE_DISCONNECTED;
-                debugLog("processProfileStateChanged, device=" + device + ", a2dpDisconnected="
-                        + a2dpDisconnected + ", hsDisconnected=" + hsDisconnected);
-                if (hsDisconnected && a2dpDisconnected) {
-                    removeAutoConnectFromA2dpSink(device);
-                    removeAutoConnectFromHeadset(device);
+            if (nextState == BluetoothProfile.STATE_DISCONNECTED) {
+                handleAllProfilesDisconnected(device);
+                if (prevState == BluetoothProfile.STATE_CONNECTING) {
+                    HeadsetService hsService = mFactory.getHeadsetService();
+                    boolean hsDisconnected = hsService == null
+                            || hsService.getConnectionState(device)
+                            == BluetoothProfile.STATE_DISCONNECTED;
+                    A2dpService a2dpService = mFactory.getA2dpService();
+                    boolean a2dpDisconnected = a2dpService == null
+                            || a2dpService.getConnectionState(device)
+                            == BluetoothProfile.STATE_DISCONNECTED;
+                    debugLog("processProfileStateChanged, device=" + device + ", a2dpDisconnected="
+                            + a2dpDisconnected + ", hsDisconnected=" + hsDisconnected);
+                    if (hsDisconnected && a2dpDisconnected) {
+                        removeAutoConnectFromA2dpSink(device);
+                        removeAutoConnectFromHeadset(device);
+                    }
                 }
             }
         }
@@ -326,6 +329,45 @@
         }
     }
 
+    private boolean handleAllProfilesDisconnected(BluetoothDevice device) {
+        boolean atLeastOneProfileConnectedForDevice = false;
+        boolean allProfilesEmpty = true;
+        HeadsetService hsService = mFactory.getHeadsetService();
+        A2dpService a2dpService = mFactory.getA2dpService();
+        PanService panService = mFactory.getPanService();
+
+        if (hsService != null) {
+            List<BluetoothDevice> hsConnDevList = hsService.getConnectedDevices();
+            allProfilesEmpty &= hsConnDevList.isEmpty();
+            atLeastOneProfileConnectedForDevice |= hsConnDevList.contains(device);
+        }
+        if (a2dpService != null) {
+            List<BluetoothDevice> a2dpConnDevList = a2dpService.getConnectedDevices();
+            allProfilesEmpty &= a2dpConnDevList.isEmpty();
+            atLeastOneProfileConnectedForDevice |= a2dpConnDevList.contains(device);
+        }
+        if (panService != null) {
+            List<BluetoothDevice> panConnDevList = panService.getConnectedDevices();
+            allProfilesEmpty &= panConnDevList.isEmpty();
+            atLeastOneProfileConnectedForDevice |= panConnDevList.contains(device);
+        }
+
+        if (!atLeastOneProfileConnectedForDevice) {
+            // Consider this device as fully disconnected, don't bother connecting others
+            debugLog("handleAllProfilesDisconnected: all profiles disconnected for " + device);
+            mHeadsetRetrySet.remove(device);
+            mA2dpRetrySet.remove(device);
+            if (allProfilesEmpty) {
+                debugLog("handleAllProfilesDisconnected: all profiles disconnected for all"
+                        + " devices");
+                // reset retry status so that in the next round we can start retrying connections
+                resetStates();
+            }
+            return true;
+        }
+        return false;
+    }
+
     private void resetStates() {
         mHeadsetRetrySet.clear();
         mA2dpRetrySet.clear();
@@ -410,45 +452,15 @@
             warnLog("processConnectOtherProfiles, adapter is not ON " + mAdapterService.getState());
             return;
         }
+        if (handleAllProfilesDisconnected(device)) {
+            debugLog("processConnectOtherProfiles: all profiles disconnected for " + device);
+            return;
+        }
+
         HeadsetService hsService = mFactory.getHeadsetService();
         A2dpService a2dpService = mFactory.getA2dpService();
         PanService panService = mFactory.getPanService();
 
-        boolean atLeastOneProfileConnectedForDevice = false;
-        boolean allProfilesEmpty = true;
-        List<BluetoothDevice> a2dpConnDevList = null;
-        List<BluetoothDevice> hsConnDevList = null;
-        List<BluetoothDevice> panConnDevList = null;
-
-        if (hsService != null) {
-            hsConnDevList = hsService.getConnectedDevices();
-            allProfilesEmpty &= hsConnDevList.isEmpty();
-            atLeastOneProfileConnectedForDevice |= hsConnDevList.contains(device);
-        }
-        if (a2dpService != null) {
-            a2dpConnDevList = a2dpService.getConnectedDevices();
-            allProfilesEmpty &= a2dpConnDevList.isEmpty();
-            atLeastOneProfileConnectedForDevice |= a2dpConnDevList.contains(device);
-        }
-        if (panService != null) {
-            panConnDevList = panService.getConnectedDevices();
-            allProfilesEmpty &= panConnDevList.isEmpty();
-            atLeastOneProfileConnectedForDevice |= panConnDevList.contains(device);
-        }
-
-        if (!atLeastOneProfileConnectedForDevice) {
-            // Consider this device as fully disconnected, don't bother connecting others
-            debugLog("processConnectOtherProfiles, all profiles disconnected for " + device);
-            mHeadsetRetrySet.remove(device);
-            mA2dpRetrySet.remove(device);
-            if (allProfilesEmpty) {
-                debugLog("processConnectOtherProfiles, all profiles disconnected for all devices");
-                // reset retry status so that in the next round we can start retrying connections
-                resetStates();
-            }
-            return;
-        }
-
         if (hsService != null) {
             if (!mHeadsetRetrySet.contains(device) && (hsService.getPriority(device)
                     >= BluetoothProfile.PRIORITY_ON) && (hsService.getConnectionState(device)
@@ -468,6 +480,7 @@
             }
         }
         if (panService != null) {
+            List<BluetoothDevice> panConnDevList = panService.getConnectedDevices();
             // TODO: the panConnDevList.isEmpty() check below should be removed once
             // Multi-PAN is supported.
             if (panConnDevList.isEmpty() && (panService.getPriority(device)
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index 011177e..56b8b9c 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -30,12 +30,13 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelUuid;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
+import android.util.StatsLog;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.hfp.HeadsetHalConstants;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -79,7 +80,8 @@
                 case MESSAGE_UUID_INTENT:
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
                     if (device != null) {
-                        sendUuidIntent(device);
+                        DeviceProperties prop = getDeviceProperties(device);
+                        sendUuidIntent(device, prop);
                     }
                     break;
             }
@@ -273,7 +275,6 @@
                 return mRssi;
             }
         }
-
         /**
          * @return mDeviceType
          */
@@ -319,6 +320,7 @@
                     without waiting for the ACTION_UUID intent.
                     This was resulting in multiple calls to connect().*/
                     mUuids = null;
+                    mAlias = null;
                 }
             }
         }
@@ -366,8 +368,7 @@
         }
     }
 
-    private void sendUuidIntent(BluetoothDevice device) {
-        DeviceProperties prop = getDeviceProperties(device);
+    private void sendUuidIntent(BluetoothDevice device, DeviceProperties prop) {
         Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothDevice.EXTRA_UUID, prop == null ? null : prop.mUuids);
@@ -455,6 +456,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, batteryLevel);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
     }
 
@@ -547,7 +549,7 @@
                             device.mUuids = newUuids;
                             if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) {
                                 sAdapterService.deviceUuidUpdated(bdDevice);
-                                sendUuidIntent(bdDevice);
+                                sendUuidIntent(bdDevice, device);
                             }
                             break;
                         case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE:
@@ -583,9 +585,15 @@
         intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);
         intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);
 
-        sAdapterService.sendBroadcastMultiplePermissions(intent, new String[]{
-                AdapterService.BLUETOOTH_PERM, android.Manifest.permission.ACCESS_COARSE_LOCATION
-        });
+        final ArrayList<DiscoveringPackage> packages = sAdapterService.getDiscoveringPackages();
+        synchronized (packages) {
+            for (DiscoveringPackage pkg : packages) {
+                intent.setPackage(pkg.getPackageName());
+                sAdapterService.sendBroadcastMultiplePermissions(intent, new String[]{
+                        AdapterService.BLUETOOTH_PERM, pkg.getPermission()
+                });
+            }
+        }
     }
 
     void aclStateChangeCallback(int status, byte[] address, int newState) {
@@ -632,6 +640,11 @@
                             + " Disconnected: " + device);
         }
 
+        int connectionState = newState == AbstractionLayer.BT_ACL_STATE_CONNECTED
+                ? BluetoothAdapter.STATE_CONNECTED : BluetoothAdapter.STATE_DISCONNECTED;
+        StatsLog.write(StatsLog.BLUETOOTH_ACL_CONNECTION_STATE_CHANGED,
+                sAdapterService.obfuscateAddress(device), connectionState);
+
         if (intent != null) {
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
@@ -831,13 +844,13 @@
         }
         int batteryLevel = (Integer) args[1];
         int numberOfLevels = (Integer) args[2];
-        if (batteryLevel < 0 || numberOfLevels < 0 || batteryLevel > numberOfLevels) {
+        if (batteryLevel < 0 || numberOfLevels <= 1 || batteryLevel > numberOfLevels) {
             Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong event value, batteryLevel="
                     + String.valueOf(batteryLevel) + ", numberOfLevels=" + String.valueOf(
                     numberOfLevels));
             return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
         }
-        return batteryLevel * 100 / numberOfLevels;
+        return batteryLevel * 100 / (numberOfLevels - 1);
     }
 
     private static void errorLog(String msg) {
diff --git a/src/com/android/bluetooth/btservice/ServiceFactory.java b/src/com/android/bluetooth/btservice/ServiceFactory.java
index 2bd7ab5..fe79a5c 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.avrcp.AvrcpTargetService;
 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 AvrcpTargetService getAvrcpTargetService() {
+        return AvrcpTargetService.get();
+    }
 }
diff --git a/src/com/android/bluetooth/btservice/SilenceDeviceManager.java b/src/com/android/bluetooth/btservice/SilenceDeviceManager.java
new file mode 100644
index 0000000..e8ec235
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/SilenceDeviceManager.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.hfp.HeadsetService;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The silence device manager controls silence mode for A2DP, HFP, and AVRCP.
+ *
+ * 1) If an active device (for A2DP or HFP) enters silence mode, the active device
+ *    for that profile will be set to null.
+ * 2) If a device exits silence mode while the A2DP or HFP active device is null,
+ *    the device will be set as the active device for that profile.
+ * 3) If a device is disconnected, it exits silence mode.
+ * 4) If a device is set as the active device for A2DP or HFP, while silence mode
+ *    is enabled, then the device will exit silence mode.
+ * 5) If a device is in silence mode, AVRCP position change event and HFP AG indicators
+ *    will be disabled.
+ * 6) If a device is not connected with A2DP or HFP, it cannot enter silence mode.
+ */
+public class SilenceDeviceManager {
+    private static final boolean DBG = true;
+    private static final boolean VERBOSE = false;
+    private static final String TAG = "SilenceDeviceManager";
+
+    private final AdapterService mAdapterService;
+    private final ServiceFactory mFactory;
+    private Handler mHandler = null;
+    private Looper mLooper = null;
+
+    private final Map<BluetoothDevice, Boolean> mSilenceDevices = new HashMap<>();
+    private final List<BluetoothDevice> mA2dpConnectedDevices = new ArrayList<>();
+    private final List<BluetoothDevice> mHfpConnectedDevices = new ArrayList<>();
+
+    private static final int MSG_SILENCE_DEVICE_STATE_CHANGED = 1;
+    private static final int MSG_A2DP_CONNECTION_STATE_CHANGED = 10;
+    private static final int MSG_HFP_CONNECTION_STATE_CHANGED = 11;
+    private static final int MSG_A2DP_ACTIVE_DEIVCE_CHANGED = 20;
+    private static final int MSG_HFP_ACTIVE_DEVICE_CHANGED = 21;
+    private static final int ENABLE_SILENCE = 0;
+    private static final int DISABLE_SILENCE = 1;
+
+    // Broadcast receiver for all changes
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) {
+                Log.e(TAG, "Received intent with null action");
+                return;
+            }
+            switch (action) {
+                case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
+                    mHandler.obtainMessage(MSG_A2DP_CONNECTION_STATE_CHANGED,
+                                           intent).sendToTarget();
+                    break;
+                case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
+                    mHandler.obtainMessage(MSG_HFP_CONNECTION_STATE_CHANGED,
+                                           intent).sendToTarget();
+                    break;
+                case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
+                    mHandler.obtainMessage(MSG_A2DP_ACTIVE_DEIVCE_CHANGED,
+                                           intent).sendToTarget();
+                    break;
+                case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
+                    mHandler.obtainMessage(MSG_HFP_ACTIVE_DEVICE_CHANGED,
+                        intent).sendToTarget();
+                    break;
+                default:
+                    Log.e(TAG, "Received unexpected intent, action=" + action);
+                    break;
+            }
+        }
+    };
+
+    class SilenceDeviceManagerHandler extends Handler {
+        SilenceDeviceManagerHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (VERBOSE) {
+                Log.d(TAG, "handleMessage: " + msg.what);
+            }
+            switch (msg.what) {
+                case MSG_SILENCE_DEVICE_STATE_CHANGED: {
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    boolean state = (msg.arg1 == ENABLE_SILENCE);
+                    handleSilenceDeviceStateChanged(device, state);
+                }
+                break;
+
+                case MSG_A2DP_CONNECTION_STATE_CHANGED: {
+                    Intent intent = (Intent) msg.obj;
+                    BluetoothDevice device =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
+                    int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+
+                    if (nextState == BluetoothProfile.STATE_CONNECTED) {
+                        // enter connected state
+                        addConnectedDevice(device, BluetoothProfile.A2DP);
+                        if (!mSilenceDevices.containsKey(device)) {
+                            mSilenceDevices.put(device, false);
+                        }
+                    } else if (prevState == BluetoothProfile.STATE_CONNECTED) {
+                        // exiting from connected state
+                        removeConnectedDevice(device, BluetoothProfile.A2DP);
+                        if (!isBluetoothAudioConnected(device)) {
+                            handleSilenceDeviceStateChanged(device, false);
+                            mSilenceDevices.remove(device);
+                        }
+                    }
+                }
+                break;
+
+                case MSG_HFP_CONNECTION_STATE_CHANGED: {
+                    Intent intent = (Intent) msg.obj;
+                    BluetoothDevice device =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
+                    int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+
+                    if (nextState == BluetoothProfile.STATE_CONNECTED) {
+                        // enter connected state
+                        addConnectedDevice(device, BluetoothProfile.HEADSET);
+                        if (!mSilenceDevices.containsKey(device)) {
+                            mSilenceDevices.put(device, false);
+                        }
+                    } else if (prevState == BluetoothProfile.STATE_CONNECTED) {
+                        // exiting from connected state
+                        removeConnectedDevice(device, BluetoothProfile.HEADSET);
+                        if (!isBluetoothAudioConnected(device)) {
+                            handleSilenceDeviceStateChanged(device, false);
+                            mSilenceDevices.remove(device);
+                        }
+                    }
+                }
+                break;
+
+                case MSG_A2DP_ACTIVE_DEIVCE_CHANGED: {
+                    Intent intent = (Intent) msg.obj;
+                    BluetoothDevice a2dpActiveDevice =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    if (getSilenceMode(a2dpActiveDevice)) {
+                        // Resume the device from silence mode.
+                        setSilenceMode(a2dpActiveDevice, false);
+                    }
+                }
+                break;
+
+                case MSG_HFP_ACTIVE_DEVICE_CHANGED: {
+                    Intent intent = (Intent) msg.obj;
+                    BluetoothDevice hfpActiveDevice =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    if (getSilenceMode(hfpActiveDevice)) {
+                        // Resume the device from silence mode.
+                        setSilenceMode(hfpActiveDevice, false);
+                    }
+                }
+                break;
+
+                default: {
+                    Log.e(TAG, "Unknown message: " + msg.what);
+                }
+                break;
+            }
+        }
+    };
+
+    SilenceDeviceManager(AdapterService service, ServiceFactory factory, Looper looper) {
+        mAdapterService = service;
+        mFactory = factory;
+        mLooper = looper;
+    }
+
+    void start() {
+        if (VERBOSE) {
+            Log.v(TAG, "start()");
+        }
+        mHandler = new SilenceDeviceManagerHandler(mLooper);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+        mAdapterService.registerReceiver(mReceiver, filter);
+    }
+
+    void cleanup() {
+        if (VERBOSE) {
+            Log.v(TAG, "cleanup()");
+        }
+        mSilenceDevices.clear();
+        mAdapterService.unregisterReceiver(mReceiver);
+    }
+
+    @VisibleForTesting
+    boolean setSilenceMode(BluetoothDevice device, boolean silence) {
+        if (mHandler == null) {
+            Log.e(TAG, "setSilenceMode() mHandler is null!");
+            return false;
+        }
+        Log.d(TAG, "setSilenceMode: " + device.getAddress() + ", " + silence);
+        Message message = mHandler.obtainMessage(MSG_SILENCE_DEVICE_STATE_CHANGED,
+                silence ? ENABLE_SILENCE : DISABLE_SILENCE, 0, device);
+        mHandler.sendMessage(message);
+        return true;
+    }
+
+    void handleSilenceDeviceStateChanged(BluetoothDevice device, boolean state) {
+        boolean oldState = getSilenceMode(device);
+        if (oldState == state) {
+            return;
+        }
+        if (!isBluetoothAudioConnected(device)) {
+            if (oldState) {
+                // Device is disconnected, resume all silenced profiles.
+                state = false;
+            } else {
+                Log.d(TAG, "Deivce is not connected to any Bluetooth audio.");
+                return;
+            }
+        }
+        mSilenceDevices.replace(device, state);
+
+        A2dpService a2dpService = mFactory.getA2dpService();
+        if (a2dpService != null) {
+            a2dpService.setSilenceMode(device, state);
+        }
+        HeadsetService headsetService = mFactory.getHeadsetService();
+        if (headsetService != null) {
+            headsetService.setSilenceMode(device, state);
+        }
+        Log.i(TAG, "Silence mode change " + device.getAddress() + ": " + oldState + " -> "
+                + state);
+        broadcastSilenceStateChange(device, state);
+    }
+
+    void broadcastSilenceStateChange(BluetoothDevice device, boolean state) {
+        Intent intent = new Intent(BluetoothDevice.ACTION_SILENCE_MODE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM);
+
+    }
+
+    @VisibleForTesting
+    boolean getSilenceMode(BluetoothDevice device) {
+        boolean state = false;
+        if (mSilenceDevices.containsKey(device)) {
+            state = mSilenceDevices.get(device);
+        }
+        return state;
+    }
+
+    void addConnectedDevice(BluetoothDevice device, int profile) {
+        if (VERBOSE) {
+            Log.d(TAG, "addConnectedDevice: " + device.getAddress() + ", profile:" + profile);
+        }
+        switch (profile) {
+            case BluetoothProfile.A2DP:
+                if (!mA2dpConnectedDevices.contains(device)) {
+                    mA2dpConnectedDevices.add(device);
+                }
+                break;
+            case BluetoothProfile.HEADSET:
+                if (!mHfpConnectedDevices.contains(device)) {
+                    mHfpConnectedDevices.add(device);
+                }
+                break;
+        }
+    }
+
+    void removeConnectedDevice(BluetoothDevice device, int profile) {
+        if (VERBOSE) {
+            Log.d(TAG, "removeConnectedDevice: " + device.getAddress() + ", profile:" + profile);
+        }
+        switch (profile) {
+            case BluetoothProfile.A2DP:
+                if (mA2dpConnectedDevices.contains(device)) {
+                    mA2dpConnectedDevices.remove(device);
+                }
+                break;
+            case BluetoothProfile.HEADSET:
+                if (mHfpConnectedDevices.contains(device)) {
+                    mHfpConnectedDevices.remove(device);
+                }
+                break;
+        }
+    }
+
+    boolean isBluetoothAudioConnected(BluetoothDevice device) {
+        return (mA2dpConnectedDevices.contains(device) || mHfpConnectedDevices.contains(device));
+    }
+
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        writer.println("\nSilenceDeviceManager:");
+        writer.println("  Address            | Is silenced?");
+        for (BluetoothDevice device : mSilenceDevices.keySet()) {
+            writer.println("  " + device.getAddress() + "  | " + getSilenceMode(device));
+        }
+    }
+
+    @VisibleForTesting
+    BroadcastReceiver getBroadcastReceiver() {
+        return mReceiver;
+    }
+}
diff --git a/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java b/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
new file mode 100644
index 0000000..d0bddf1
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice.storage;
+
+import androidx.room.Entity;
+
+@Entity
+class CustomizedMetadataEntity {
+    public byte[] manufacturer_name;
+    public byte[] model_name;
+    public byte[] software_version;
+    public byte[] hardware_version;
+    public byte[] companion_app;
+    public byte[] main_icon;
+    public byte[] is_untethered_headset;
+    public byte[] untethered_left_icon;
+    public byte[] untethered_right_icon;
+    public byte[] untethered_case_icon;
+    public byte[] untethered_left_battery;
+    public byte[] untethered_right_battery;
+    public byte[] untethered_case_battery;
+    public byte[] untethered_left_charging;
+    public byte[] untethered_right_charging;
+    public byte[] untethered_case_charging;
+    public byte[] enhanced_settings_ui_uri;
+}
diff --git a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
new file mode 100644
index 0000000..a0e3c5a
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
@@ -0,0 +1,796 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice.storage;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProtoEnums;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.StatsLog;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The active device manager is responsible to handle a Room database
+ * for Bluetooth persistent data.
+ */
+public class DatabaseManager {
+    private static final boolean DBG = true;
+    private static final boolean VERBOSE = true;
+    private static final String TAG = "BluetoothDatabase";
+
+    private AdapterService mAdapterService = null;
+    private HandlerThread mHandlerThread = null;
+    private Handler mHandler = null;
+    private MetadataDatabase mDatabase = null;
+    private boolean mMigratedFromSettingsGlobal = false;
+
+    @VisibleForTesting
+    final Map<String, Metadata> mMetadataCache = new HashMap<>();
+    private final Semaphore mSemaphore = new Semaphore(1);
+
+    private static final int LOAD_DATABASE_TIMEOUT = 500; // milliseconds
+    private static final int MSG_LOAD_DATABASE = 0;
+    private static final int MSG_UPDATE_DATABASE = 1;
+    private static final int MSG_DELETE_DATABASE = 2;
+    private static final int MSG_CLEAR_DATABASE = 100;
+    private static final String LOCAL_STORAGE = "LocalStorage";
+
+    /**
+     * Constructor of the DatabaseManager
+     */
+    public DatabaseManager(AdapterService service) {
+        mAdapterService = service;
+    }
+
+    class DatabaseHandler extends Handler {
+        DatabaseHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_LOAD_DATABASE: {
+                    synchronized (mDatabase) {
+                        List<Metadata> list;
+                        try {
+                            list = mDatabase.load();
+                        } catch (IllegalStateException e) {
+                            Log.e(TAG, "Unable to open database: " + e);
+                            mDatabase = MetadataDatabase
+                                    .createDatabaseWithoutMigration(mAdapterService);
+                            list = mDatabase.load();
+                        }
+                        cacheMetadata(list);
+                    }
+                    break;
+                }
+                case MSG_UPDATE_DATABASE: {
+                    Metadata data = (Metadata) msg.obj;
+                    synchronized (mDatabase) {
+                        mDatabase.insert(data);
+                    }
+                    break;
+                }
+                case MSG_DELETE_DATABASE: {
+                    String address = (String) msg.obj;
+                    synchronized (mDatabase) {
+                        mDatabase.delete(address);
+                    }
+                    break;
+                }
+                case MSG_CLEAR_DATABASE: {
+                    synchronized (mDatabase) {
+                        mDatabase.deleteAll();
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) {
+                Log.e(TAG, "Received intent with null action");
+                return;
+            }
+            switch (action) {
+                case BluetoothDevice.ACTION_BOND_STATE_CHANGED: {
+                    int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+                            BluetoothDevice.ERROR);
+                    BluetoothDevice device =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    Objects.requireNonNull(device,
+                            "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
+                    bondStateChanged(device, state);
+                    break;
+                }
+                case BluetoothAdapter.ACTION_STATE_CHANGED: {
+                    int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+                            BluetoothAdapter.STATE_OFF);
+                    if (!mMigratedFromSettingsGlobal
+                            && state == BluetoothAdapter.STATE_TURNING_ON) {
+                        migrateSettingsGlobal();
+                    }
+                    break;
+                }
+            }
+        }
+    };
+
+    void bondStateChanged(BluetoothDevice device, int state) {
+        synchronized (mMetadataCache) {
+            String address = device.getAddress();
+            if (state != BluetoothDevice.BOND_NONE) {
+                if (mMetadataCache.containsKey(address)) {
+                    return;
+                }
+                createMetadata(address);
+            } else {
+                Metadata metadata = mMetadataCache.get(address);
+                if (metadata != null) {
+                    mMetadataCache.remove(address);
+                    deleteDatabase(metadata);
+                }
+            }
+        }
+    }
+
+    boolean isValidMetaKey(int key) {
+        switch (key) {
+            case BluetoothDevice.METADATA_MANUFACTURER_NAME:
+            case BluetoothDevice.METADATA_MODEL_NAME:
+            case BluetoothDevice.METADATA_SOFTWARE_VERSION:
+            case BluetoothDevice.METADATA_HARDWARE_VERSION:
+            case BluetoothDevice.METADATA_COMPANION_APP:
+            case BluetoothDevice.METADATA_MAIN_ICON:
+            case BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET:
+            case BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON:
+            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON:
+            case BluetoothDevice.METADATA_UNTETHERED_CASE_ICON:
+            case BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY:
+            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY:
+            case BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY:
+            case BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING:
+            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING:
+            case BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING:
+            case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI:
+                return true;
+        }
+        Log.w(TAG, "Invalid metadata key " + key);
+        return false;
+    }
+
+    /**
+     * Set customized metadata to database with requested key
+     */
+    @VisibleForTesting
+    public boolean setCustomMeta(BluetoothDevice device, int key, byte[] newValue) {
+        synchronized (mMetadataCache) {
+            if (device == null) {
+                Log.e(TAG, "setCustomMeta: device is null");
+                return false;
+            }
+            if (!isValidMetaKey(key)) {
+                Log.e(TAG, "setCustomMeta: meta key invalid " + key);
+                return false;
+            }
+
+            String address = device.getAddress();
+            if (VERBOSE) {
+                Log.d(TAG, "setCustomMeta: " + address + ", key=" + key);
+            }
+            if (!mMetadataCache.containsKey(address)) {
+                createMetadata(address);
+            }
+            Metadata data = mMetadataCache.get(address);
+            byte[] oldValue = data.getCustomizedMeta(key);
+            if (oldValue != null && Arrays.equals(oldValue, newValue)) {
+                if (VERBOSE) {
+                    Log.d(TAG, "setCustomMeta: metadata not changed.");
+                }
+                return true;
+            }
+            logManufacturerInfo(device, key, newValue);
+            data.setCustomizedMeta(key, newValue);
+
+            updateDatabase(data);
+            mAdapterService.metadataChanged(address, key, newValue);
+            return true;
+        }
+    }
+
+    /**
+     * Get customized metadata from database with requested key
+     */
+    @VisibleForTesting
+    public byte[] getCustomMeta(BluetoothDevice device, int key) {
+        synchronized (mMetadataCache) {
+            if (device == null) {
+                Log.e(TAG, "getCustomMeta: device is null");
+                return null;
+            }
+            if (!isValidMetaKey(key)) {
+                Log.e(TAG, "getCustomMeta: meta key invalid " + key);
+                return null;
+            }
+
+            String address = device.getAddress();
+
+            if (!mMetadataCache.containsKey(address)) {
+                Log.e(TAG, "getCustomMeta: device " + address + " is not in cache");
+                return null;
+            }
+
+            Metadata data = mMetadataCache.get(address);
+            return data.getCustomizedMeta(key);
+        }
+    }
+
+    /**
+     * Set the device profile prioirty
+     *
+     * @param device {@link BluetoothDevice} wish to set
+     * @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET},
+     * {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP},
+     * {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST},
+     * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP},
+     * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
+     * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
+     * {@link BluetoothProfile#HEARING_AID}
+     * @param newPriority the priority to set; one of
+     * {@link BluetoothProfile#PRIORITY_UNDEFINED},
+     * {@link BluetoothProfile#PRIORITY_OFF},
+     * {@link BluetoothProfile#PRIORITY_ON},
+     * {@link BluetoothProfile#PRIORITY_AUTO_CONNECT}
+     */
+    @VisibleForTesting
+    public boolean setProfilePriority(BluetoothDevice device, int profile, int newPriority) {
+        synchronized (mMetadataCache) {
+            if (device == null) {
+                Log.e(TAG, "setProfilePriority: device is null");
+                return false;
+            }
+
+            if (newPriority != BluetoothProfile.PRIORITY_UNDEFINED
+                    && newPriority != BluetoothProfile.PRIORITY_OFF
+                    && newPriority != BluetoothProfile.PRIORITY_ON
+                    && newPriority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+                Log.e(TAG, "setProfilePriority: invalid priority " + newPriority);
+                return false;
+            }
+
+            String address = device.getAddress();
+            if (VERBOSE) {
+                Log.v(TAG, "setProfilePriority: " + address + ", profile=" + profile
+                        + ", priority = " + newPriority);
+            }
+            if (!mMetadataCache.containsKey(address)) {
+                if (newPriority == BluetoothProfile.PRIORITY_UNDEFINED) {
+                    return true;
+                }
+                createMetadata(address);
+            }
+            Metadata data = mMetadataCache.get(address);
+            int oldPriority = data.getProfilePriority(profile);
+            if (oldPriority == newPriority) {
+                if (VERBOSE) {
+                    Log.v(TAG, "setProfilePriority priority not changed.");
+                }
+                return true;
+            }
+
+            data.setProfilePriority(profile, newPriority);
+            updateDatabase(data);
+            return true;
+        }
+    }
+
+    /**
+     * Get the device profile prioirty
+     *
+     * @param device {@link BluetoothDevice} wish to get
+     * @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET},
+     * {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP},
+     * {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST},
+     * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP},
+     * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
+     * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
+     * {@link BluetoothProfile#HEARING_AID}
+     * @return the profile priority of the device; one of
+     * {@link BluetoothProfile#PRIORITY_UNDEFINED},
+     * {@link BluetoothProfile#PRIORITY_OFF},
+     * {@link BluetoothProfile#PRIORITY_ON},
+     * {@link BluetoothProfile#PRIORITY_AUTO_CONNECT}
+     */
+    @VisibleForTesting
+    public int getProfilePriority(BluetoothDevice device, int profile) {
+        synchronized (mMetadataCache) {
+            if (device == null) {
+                Log.e(TAG, "getProfilePriority: device is null");
+                return BluetoothProfile.PRIORITY_UNDEFINED;
+            }
+
+            String address = device.getAddress();
+
+            if (!mMetadataCache.containsKey(address)) {
+                Log.e(TAG, "getProfilePriority: device " + address + " is not in cache");
+                return BluetoothProfile.PRIORITY_UNDEFINED;
+            }
+
+            Metadata data = mMetadataCache.get(address);
+            int priority = data.getProfilePriority(profile);
+            if (VERBOSE) {
+                Log.v(TAG, "getProfilePriority: " + address + ", profile=" + profile
+                        + ", priority = " + priority);
+            }
+            return priority;
+        }
+    }
+
+    /**
+     * Set the A2DP optional coedc support value
+     *
+     * @param device {@link BluetoothDevice} wish to set
+     * @param newValue the new A2DP optional coedc support value, one of
+     * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORT_UNKNOWN},
+     * {@link BluetoothA2dp#OPTIONAL_CODECS_NOT_SUPPORTED},
+     * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORTED}
+     */
+    @VisibleForTesting
+    public void setA2dpSupportsOptionalCodecs(BluetoothDevice device, int newValue) {
+        synchronized (mMetadataCache) {
+            if (device == null) {
+                Log.e(TAG, "setA2dpOptionalCodec: device is null");
+                return;
+            }
+            if (newValue != BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
+                    && newValue != BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED
+                    && newValue != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+                Log.e(TAG, "setA2dpSupportsOptionalCodecs: invalid value " + newValue);
+                return;
+            }
+
+            String address = device.getAddress();
+
+            if (!mMetadataCache.containsKey(address)) {
+                return;
+            }
+            Metadata data = mMetadataCache.get(address);
+            int oldValue = data.a2dpSupportsOptionalCodecs;
+            if (oldValue == newValue) {
+                return;
+            }
+
+            data.a2dpSupportsOptionalCodecs = newValue;
+            updateDatabase(data);
+        }
+    }
+
+    /**
+     * Get the A2DP optional coedc support value
+     *
+     * @param device {@link BluetoothDevice} wish to get
+     * @return the A2DP optional coedc support value, one of
+     * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORT_UNKNOWN},
+     * {@link BluetoothA2dp#OPTIONAL_CODECS_NOT_SUPPORTED},
+     * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORTED},
+     */
+    @VisibleForTesting
+    public int getA2dpSupportsOptionalCodecs(BluetoothDevice device) {
+        synchronized (mMetadataCache) {
+            if (device == null) {
+                Log.e(TAG, "setA2dpOptionalCodec: device is null");
+                return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
+            }
+
+            String address = device.getAddress();
+
+            if (!mMetadataCache.containsKey(address)) {
+                Log.e(TAG, "getA2dpOptionalCodec: device " + address + " is not in cache");
+                return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
+            }
+
+            Metadata data = mMetadataCache.get(address);
+            return data.a2dpSupportsOptionalCodecs;
+        }
+    }
+
+    /**
+     * Set the A2DP optional coedc enabled value
+     *
+     * @param device {@link BluetoothDevice} wish to set
+     * @param newValue the new A2DP optional coedc enabled value, one of
+     * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_UNKNOWN},
+     * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_DISABLED},
+     * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_ENABLED}
+     */
+    @VisibleForTesting
+    public void setA2dpOptionalCodecsEnabled(BluetoothDevice device, int newValue) {
+        synchronized (mMetadataCache) {
+            if (device == null) {
+                Log.e(TAG, "setA2dpOptionalCodecEnabled: device is null");
+                return;
+            }
+            if (newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
+                    && newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
+                    && newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
+                Log.e(TAG, "setA2dpOptionalCodecsEnabled: invalid value " + newValue);
+                return;
+            }
+
+            String address = device.getAddress();
+
+            if (!mMetadataCache.containsKey(address)) {
+                return;
+            }
+            Metadata data = mMetadataCache.get(address);
+            int oldValue = data.a2dpOptionalCodecsEnabled;
+            if (oldValue == newValue) {
+                return;
+            }
+
+            data.a2dpOptionalCodecsEnabled = newValue;
+            updateDatabase(data);
+        }
+    }
+
+    /**
+     * Get the A2DP optional coedc enabled value
+     *
+     * @param device {@link BluetoothDevice} wish to get
+     * @return the A2DP optional coedc enabled value, one of
+     * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_UNKNOWN},
+     * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_DISABLED},
+     * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_ENABLED}
+     */
+    @VisibleForTesting
+    public int getA2dpOptionalCodecsEnabled(BluetoothDevice device) {
+        synchronized (mMetadataCache) {
+            if (device == null) {
+                Log.e(TAG, "getA2dpOptionalCodecEnabled: device is null");
+                return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
+            }
+
+            String address = device.getAddress();
+
+            if (!mMetadataCache.containsKey(address)) {
+                Log.e(TAG, "getA2dpOptionalCodecEnabled: device " + address + " is not in cache");
+                return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
+            }
+
+            Metadata data = mMetadataCache.get(address);
+            return data.a2dpOptionalCodecsEnabled;
+        }
+    }
+
+    /**
+     * Get the {@link Looper} for the handler thread. This is used in testing and helper
+     * objects
+     *
+     * @return {@link Looper} for the handler thread
+     */
+    @VisibleForTesting
+    public Looper getHandlerLooper() {
+        if (mHandlerThread == null) {
+            return null;
+        }
+        return mHandlerThread.getLooper();
+    }
+
+    /**
+     * Start and initialize the DatabaseManager
+     *
+     * @param database the Bluetooth storage {@link MetadataDatabase}
+     */
+    public void start(MetadataDatabase database) {
+        if (DBG) {
+            Log.d(TAG, "start()");
+        }
+
+        if (mAdapterService == null) {
+            Log.e(TAG, "stat failed, mAdapterService is null.");
+            return;
+        }
+
+        if (database == null) {
+            Log.e(TAG, "stat failed, database is null.");
+            return;
+        }
+
+        mDatabase = database;
+
+        mHandlerThread = new HandlerThread("BluetoothDatabaseManager");
+        mHandlerThread.start();
+        mHandler = new DatabaseHandler(mHandlerThread.getLooper());
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+        mAdapterService.registerReceiver(mReceiver, filter);
+
+        loadDatabase();
+    }
+
+    String getDatabaseAbsolutePath() {
+        //TODO backup database when Bluetooth turn off and FOTA?
+        return mAdapterService.getDatabasePath(MetadataDatabase.DATABASE_NAME)
+                .getAbsolutePath();
+    }
+
+    /**
+     * Clear all persistence data in database
+     */
+    public void factoryReset() {
+        Log.w(TAG, "factoryReset");
+        Message message = mHandler.obtainMessage(MSG_CLEAR_DATABASE);
+        mHandler.sendMessage(message);
+    }
+
+    /**
+     * Close and de-init the DatabaseManager
+     */
+    public void cleanup() {
+        removeUnusedMetadata();
+        mAdapterService.unregisterReceiver(mReceiver);
+        if (mHandlerThread != null) {
+            mHandlerThread.quit();
+            mHandlerThread = null;
+        }
+        mMetadataCache.clear();
+    }
+
+    void createMetadata(String address) {
+        if (VERBOSE) {
+            Log.v(TAG, "createMetadata " + address);
+        }
+        Metadata data = new Metadata(address);
+        mMetadataCache.put(address, data);
+        updateDatabase(data);
+    }
+
+    @VisibleForTesting
+    void removeUnusedMetadata() {
+        BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+        synchronized (mMetadataCache) {
+            mMetadataCache.forEach((address, metadata) -> {
+                if (!address.equals(LOCAL_STORAGE)
+                        && !Arrays.asList(bondedDevices).stream().anyMatch(device ->
+                        address.equals(device.getAddress()))) {
+                    List<Integer> list = metadata.getChangedCustomizedMeta();
+                    for (int key : list) {
+                        mAdapterService.metadataChanged(address, key, null);
+                    }
+                    Log.i(TAG, "remove unpaired device from database " + address);
+                    deleteDatabase(mMetadataCache.get(address));
+                }
+            });
+        }
+    }
+
+    void cacheMetadata(List<Metadata> list) {
+        synchronized (mMetadataCache) {
+            Log.i(TAG, "cacheMetadata");
+            // Unlock the main thread.
+            mSemaphore.release();
+
+            if (!isMigrated(list)) {
+                // Wait for data migrate from Settings Global
+                mMigratedFromSettingsGlobal = false;
+                return;
+            }
+            mMigratedFromSettingsGlobal = true;
+            for (Metadata data : list) {
+                String address = data.getAddress();
+                if (VERBOSE) {
+                    Log.v(TAG, "cacheMetadata: found device " + address);
+                }
+                mMetadataCache.put(address, data);
+            }
+            if (VERBOSE) {
+                Log.v(TAG, "cacheMetadata: Database is ready");
+            }
+        }
+    }
+
+    boolean isMigrated(List<Metadata> list) {
+        for (Metadata data : list) {
+            String address = data.getAddress();
+            if (address.equals(LOCAL_STORAGE) && data.migrated) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void migrateSettingsGlobal() {
+        Log.i(TAG, "migrateSettingGlobal");
+
+        BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+        ContentResolver contentResolver = mAdapterService.getContentResolver();
+
+        for (BluetoothDevice device : bondedDevices) {
+            int a2dpPriority = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
+                    BluetoothProfile.PRIORITY_UNDEFINED);
+            int a2dpSinkPriority = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
+                    BluetoothProfile.PRIORITY_UNDEFINED);
+            int hearingaidPriority = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothHearingAidPriorityKey(device.getAddress()),
+                    BluetoothProfile.PRIORITY_UNDEFINED);
+            int headsetPriority = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
+                    BluetoothProfile.PRIORITY_UNDEFINED);
+            int headsetClientPriority = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
+                    BluetoothProfile.PRIORITY_UNDEFINED);
+            int hidHostPriority = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothHidHostPriorityKey(device.getAddress()),
+                    BluetoothProfile.PRIORITY_UNDEFINED);
+            int mapPriority = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
+                    BluetoothProfile.PRIORITY_UNDEFINED);
+            int mapClientPriority = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothMapClientPriorityKey(device.getAddress()),
+                    BluetoothProfile.PRIORITY_UNDEFINED);
+            int panPriority = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothPanPriorityKey(device.getAddress()),
+                    BluetoothProfile.PRIORITY_UNDEFINED);
+            int pbapPriority = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
+                    BluetoothProfile.PRIORITY_UNDEFINED);
+            int pbapClientPriority = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
+                    BluetoothProfile.PRIORITY_UNDEFINED);
+            int sapPriority = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothSapPriorityKey(device.getAddress()),
+                    BluetoothProfile.PRIORITY_UNDEFINED);
+            int a2dpSupportsOptionalCodec = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
+                    BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+            int a2dpOptionalCodecEnabled = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()),
+                    BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+
+            String address = device.getAddress();
+            Metadata data = new Metadata(address);
+            data.setProfilePriority(BluetoothProfile.A2DP, a2dpPriority);
+            data.setProfilePriority(BluetoothProfile.A2DP_SINK, a2dpSinkPriority);
+            data.setProfilePriority(BluetoothProfile.HEADSET, headsetPriority);
+            data.setProfilePriority(BluetoothProfile.HEADSET_CLIENT, headsetClientPriority);
+            data.setProfilePriority(BluetoothProfile.HID_HOST, hidHostPriority);
+            data.setProfilePriority(BluetoothProfile.PAN, panPriority);
+            data.setProfilePriority(BluetoothProfile.PBAP, pbapPriority);
+            data.setProfilePriority(BluetoothProfile.PBAP_CLIENT, pbapClientPriority);
+            data.setProfilePriority(BluetoothProfile.MAP, mapPriority);
+            data.setProfilePriority(BluetoothProfile.MAP_CLIENT, mapClientPriority);
+            data.setProfilePriority(BluetoothProfile.SAP, sapPriority);
+            data.setProfilePriority(BluetoothProfile.HEARING_AID, hearingaidPriority);
+            data.a2dpSupportsOptionalCodecs = a2dpSupportsOptionalCodec;
+            data.a2dpOptionalCodecsEnabled = a2dpOptionalCodecEnabled;
+            mMetadataCache.put(address, data);
+            updateDatabase(data);
+        }
+
+        // Mark database migrated from Settings Global
+        Metadata localData = new Metadata(LOCAL_STORAGE);
+        localData.migrated = true;
+        mMetadataCache.put(LOCAL_STORAGE, localData);
+        updateDatabase(localData);
+
+        // Reload database after migration is completed
+        loadDatabase();
+
+    }
+
+    private void loadDatabase() {
+        if (DBG) {
+            Log.d(TAG, "Load Database");
+        }
+        Message message = mHandler.obtainMessage(MSG_LOAD_DATABASE);
+        mHandler.sendMessage(message);
+        try {
+            // Lock the thread until handler thread finish loading database.
+            mSemaphore.tryAcquire(LOAD_DATABASE_TIMEOUT, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "loadDatabase: semaphore acquire failed");
+        }
+    }
+
+    private void updateDatabase(Metadata data) {
+        if (data.getAddress() == null) {
+            Log.e(TAG, "updateDatabase: address is null");
+            return;
+        }
+        if (DBG) {
+            Log.d(TAG, "updateDatabase " + data.getAddress());
+        }
+        Message message = mHandler.obtainMessage(MSG_UPDATE_DATABASE);
+        message.obj = data;
+        mHandler.sendMessage(message);
+    }
+
+    private void deleteDatabase(Metadata data) {
+        if (data.getAddress() == null) {
+            Log.e(TAG, "deleteDatabase: address is null");
+            return;
+        }
+        Log.d(TAG, "deleteDatabase: " + data.getAddress());
+        Message message = mHandler.obtainMessage(MSG_DELETE_DATABASE);
+        message.obj = data.getAddress();
+        mHandler.sendMessage(message);
+    }
+
+    private void logManufacturerInfo(BluetoothDevice device, int key, byte[] bytesValue) {
+        String callingApp = mAdapterService.getPackageManager().getNameForUid(
+                Binder.getCallingUid());
+        String manufacturerName = "";
+        String modelName = "";
+        String hardwareVersion = "";
+        String softwareVersion = "";
+        String value = Utils.byteArrayToUtf8String(bytesValue);
+        switch (key) {
+            case BluetoothDevice.METADATA_MANUFACTURER_NAME:
+                manufacturerName = value;
+                break;
+            case BluetoothDevice.METADATA_MODEL_NAME:
+                modelName = value;
+                break;
+            case BluetoothDevice.METADATA_HARDWARE_VERSION:
+                hardwareVersion = value;
+                break;
+            case BluetoothDevice.METADATA_SOFTWARE_VERSION:
+                softwareVersion = value;
+                break;
+            default:
+                // Do not log anything if metadata doesn't fall into above categories
+                return;
+        }
+        StatsLog.write(StatsLog.BLUETOOTH_DEVICE_INFO_REPORTED,
+                mAdapterService.obfuscateAddress(device),
+                BluetoothProtoEnums.DEVICE_INFO_EXTERNAL, callingApp, manufacturerName, modelName,
+                hardwareVersion, softwareVersion);
+    }
+}
diff --git a/src/com/android/bluetooth/btservice/storage/Metadata.java b/src/com/android/bluetooth/btservice/storage/Metadata.java
new file mode 100644
index 0000000..b39333f
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/storage/Metadata.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice.storage;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+
+import androidx.annotation.NonNull;
+import androidx.room.Embedded;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity(tableName = "metadata")
+class Metadata {
+    @PrimaryKey
+    @NonNull
+    private String address;
+
+    public boolean migrated;
+
+    @Embedded
+    public ProfilePrioritiesEntity profilePriorities;
+
+    @Embedded
+    @NonNull
+    public CustomizedMetadataEntity publicMetadata;
+
+    public int a2dpSupportsOptionalCodecs;
+    public int a2dpOptionalCodecsEnabled;
+
+    Metadata(String address) {
+        this.address = address;
+        migrated = false;
+        profilePriorities = new ProfilePrioritiesEntity();
+        publicMetadata = new CustomizedMetadataEntity();
+        a2dpSupportsOptionalCodecs = BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
+        a2dpOptionalCodecsEnabled = BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
+    }
+
+    String getAddress() {
+        return address;
+    }
+
+    void setProfilePriority(int profile, int priority) {
+        switch (profile) {
+            case BluetoothProfile.A2DP:
+                profilePriorities.a2dp_priority = priority;
+                break;
+            case BluetoothProfile.A2DP_SINK:
+                profilePriorities.a2dp_sink_priority = priority;
+                break;
+            case BluetoothProfile.HEADSET:
+                profilePriorities.hfp_priority = priority;
+                break;
+            case BluetoothProfile.HEADSET_CLIENT:
+                profilePriorities.hfp_client_priority = priority;
+                break;
+            case BluetoothProfile.HID_HOST:
+                profilePriorities.hid_host_priority = priority;
+                break;
+            case BluetoothProfile.PAN:
+                profilePriorities.pan_priority = priority;
+                break;
+            case BluetoothProfile.PBAP:
+                profilePriorities.pbap_priority = priority;
+                break;
+            case BluetoothProfile.PBAP_CLIENT:
+                profilePriorities.pbap_client_priority = priority;
+                break;
+            case BluetoothProfile.MAP:
+                profilePriorities.map_priority = priority;
+                break;
+            case BluetoothProfile.MAP_CLIENT:
+                profilePriorities.map_client_priority = priority;
+                break;
+            case BluetoothProfile.SAP:
+                profilePriorities.sap_priority = priority;
+                break;
+            case BluetoothProfile.HEARING_AID:
+                profilePriorities.hearing_aid_priority = priority;
+                break;
+            default:
+                throw new IllegalArgumentException("invalid profile " + profile);
+        }
+    }
+
+    int getProfilePriority(int profile) {
+        switch (profile) {
+            case BluetoothProfile.A2DP:
+                return profilePriorities.a2dp_priority;
+            case BluetoothProfile.A2DP_SINK:
+                return profilePriorities.a2dp_sink_priority;
+            case BluetoothProfile.HEADSET:
+                return profilePriorities.hfp_priority;
+            case BluetoothProfile.HEADSET_CLIENT:
+                return profilePriorities.hfp_client_priority;
+            case BluetoothProfile.HID_HOST:
+                return profilePriorities.hid_host_priority;
+            case BluetoothProfile.PAN:
+                return profilePriorities.pan_priority;
+            case BluetoothProfile.PBAP:
+                return profilePriorities.pbap_priority;
+            case BluetoothProfile.PBAP_CLIENT:
+                return profilePriorities.pbap_client_priority;
+            case BluetoothProfile.MAP:
+                return profilePriorities.map_priority;
+            case BluetoothProfile.MAP_CLIENT:
+                return profilePriorities.map_client_priority;
+            case BluetoothProfile.SAP:
+                return profilePriorities.sap_priority;
+            case BluetoothProfile.HEARING_AID:
+                return profilePriorities.hearing_aid_priority;
+        }
+        return BluetoothProfile.PRIORITY_UNDEFINED;
+    }
+
+    void setCustomizedMeta(int key, byte[] value) {
+        switch (key) {
+            case BluetoothDevice.METADATA_MANUFACTURER_NAME:
+                publicMetadata.manufacturer_name = value;
+                break;
+            case BluetoothDevice.METADATA_MODEL_NAME:
+                publicMetadata.model_name = value;
+                break;
+            case BluetoothDevice.METADATA_SOFTWARE_VERSION:
+                publicMetadata.software_version = value;
+                break;
+            case BluetoothDevice.METADATA_HARDWARE_VERSION:
+                publicMetadata.hardware_version = value;
+                break;
+            case BluetoothDevice.METADATA_COMPANION_APP:
+                publicMetadata.companion_app = value;
+                break;
+            case BluetoothDevice.METADATA_MAIN_ICON:
+                publicMetadata.main_icon = value;
+                break;
+            case BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET:
+                publicMetadata.is_untethered_headset = value;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON:
+                publicMetadata.untethered_left_icon = value;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON:
+                publicMetadata.untethered_right_icon = value;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_CASE_ICON:
+                publicMetadata.untethered_case_icon = value;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY:
+                publicMetadata.untethered_left_battery = value;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY:
+                publicMetadata.untethered_right_battery = value;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY:
+                publicMetadata.untethered_case_battery = value;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING:
+                publicMetadata.untethered_left_charging = value;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING:
+                publicMetadata.untethered_right_charging = value;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING:
+                publicMetadata.untethered_case_charging = value;
+                break;
+            case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI:
+                publicMetadata.enhanced_settings_ui_uri = value;
+                break;
+        }
+    }
+
+    byte[] getCustomizedMeta(int key) {
+        byte[] value = null;
+        switch (key) {
+            case BluetoothDevice.METADATA_MANUFACTURER_NAME:
+                value = publicMetadata.manufacturer_name;
+                break;
+            case BluetoothDevice.METADATA_MODEL_NAME:
+                value = publicMetadata.model_name;
+                break;
+            case BluetoothDevice.METADATA_SOFTWARE_VERSION:
+                value = publicMetadata.software_version;
+                break;
+            case BluetoothDevice.METADATA_HARDWARE_VERSION:
+                value = publicMetadata.hardware_version;
+                break;
+            case BluetoothDevice.METADATA_COMPANION_APP:
+                value = publicMetadata.companion_app;
+                break;
+            case BluetoothDevice.METADATA_MAIN_ICON:
+                value = publicMetadata.main_icon;
+                break;
+            case BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET:
+                value = publicMetadata.is_untethered_headset;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON:
+                value = publicMetadata.untethered_left_icon;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON:
+                value = publicMetadata.untethered_right_icon;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_CASE_ICON:
+                value = publicMetadata.untethered_case_icon;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY:
+                value = publicMetadata.untethered_left_battery;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY:
+                value = publicMetadata.untethered_right_battery;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY:
+                value = publicMetadata.untethered_case_battery;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING:
+                value = publicMetadata.untethered_left_charging;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING:
+                value = publicMetadata.untethered_right_charging;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING:
+                value = publicMetadata.untethered_case_charging;
+                break;
+            case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI:
+                value = publicMetadata.enhanced_settings_ui_uri;
+                break;
+        }
+        return value;
+    }
+
+    List<Integer> getChangedCustomizedMeta() {
+        List<Integer> list = new ArrayList<>();
+        if (publicMetadata.manufacturer_name != null) {
+            list.add(BluetoothDevice.METADATA_MANUFACTURER_NAME);
+        }
+        if (publicMetadata.model_name != null) {
+            list.add(BluetoothDevice.METADATA_MODEL_NAME);
+        }
+        if (publicMetadata.software_version != null) {
+            list.add(BluetoothDevice.METADATA_SOFTWARE_VERSION);
+        }
+        if (publicMetadata.hardware_version != null) {
+            list.add(BluetoothDevice.METADATA_HARDWARE_VERSION);
+        }
+        if (publicMetadata.companion_app != null) {
+            list.add(BluetoothDevice.METADATA_COMPANION_APP);
+        }
+        if (publicMetadata.main_icon != null) {
+            list.add(BluetoothDevice.METADATA_MAIN_ICON);
+        }
+        if (publicMetadata.is_untethered_headset != null) {
+            list.add(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET);
+        }
+        if (publicMetadata.untethered_left_icon != null) {
+            list.add(BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON);
+        }
+        if (publicMetadata.untethered_right_icon != null) {
+            list.add(BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON);
+        }
+        if (publicMetadata.untethered_case_icon != null) {
+            list.add(BluetoothDevice.METADATA_UNTETHERED_CASE_ICON);
+        }
+        if (publicMetadata.untethered_left_battery != null) {
+            list.add(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY);
+        }
+        if (publicMetadata.untethered_right_battery != null) {
+            list.add(BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY);
+        }
+        if (publicMetadata.untethered_case_battery != null) {
+            list.add(BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY);
+        }
+        if (publicMetadata.untethered_left_charging != null) {
+            list.add(BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING);
+        }
+        if (publicMetadata.untethered_right_charging != null) {
+            list.add(BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING);
+        }
+        if (publicMetadata.untethered_case_charging != null) {
+            list.add(BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING);
+        }
+        if (publicMetadata.enhanced_settings_ui_uri != null) {
+            list.add(BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI);
+        }
+        return list;
+    }
+}
diff --git a/src/com/android/bluetooth/btservice/storage/MetadataDao.java b/src/com/android/bluetooth/btservice/storage/MetadataDao.java
new file mode 100644
index 0000000..7c5f440
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/storage/MetadataDao.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice.storage;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+
+import java.util.List;
+
+@Dao
+interface MetadataDao {
+    /**
+     * Load all items in the database
+     */
+    @Query("SELECT * FROM metadata")
+    List<Metadata> load();
+
+    /**
+     * Create or update a Metadata in the database
+     */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(Metadata... metadata);
+
+    /**
+     * Delete a Metadata in the database
+     */
+    @Query("DELETE FROM metadata WHERE address = :address")
+    void delete(String address);
+
+    /**
+     * Delete all Metadatas in the database
+     */
+    @Query("DELETE FROM metadata")
+    void deleteAll();
+}
diff --git a/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
new file mode 100644
index 0000000..fe56e3a
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice.storage;
+
+import android.content.Context;
+
+import androidx.room.Database;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+
+/**
+ * MetadataDatabase is a Room database stores Bluetooth persistence data
+ */
+@Database(entities = {Metadata.class}, version = 102)
+public abstract class MetadataDatabase extends RoomDatabase {
+    /**
+     * The database file name
+     */
+    public static final String DATABASE_NAME = "bluetooth_db";
+
+    protected abstract MetadataDao mMetadataDao();
+
+    /**
+     * Create a {@link MetadataDatabase} database with migrations
+     *
+     * @param context the Context to create database
+     * @return the created {@link MetadataDatabase}
+     */
+    public static MetadataDatabase createDatabase(Context context) {
+        return Room.databaseBuilder(context,
+                MetadataDatabase.class, DATABASE_NAME)
+                .addMigrations(MIGRATION_100_101)
+                .addMigrations(MIGRATION_101_102)
+                .build();
+    }
+
+    /**
+     * Create a {@link MetadataDatabase} database without migration, database
+     * would be reset if any load failure happens
+     *
+     * @param context the Context to create database
+     * @return the created {@link MetadataDatabase}
+     */
+    public static MetadataDatabase createDatabaseWithoutMigration(Context context) {
+        return Room.databaseBuilder(context,
+                MetadataDatabase.class, DATABASE_NAME)
+                .fallbackToDestructiveMigration()
+                .build();
+    }
+
+    /**
+     * Insert a {@link Metadata} to database
+     *
+     * @param metadata the data wish to put into storage
+     */
+    public void insert(Metadata... metadata) {
+        mMetadataDao().insert(metadata);
+    }
+
+    /**
+     * Load all data from database as a {@link List} of {@link Metadata}
+     *
+     * @return a {@link List} of {@link Metadata}
+     */
+    public List<Metadata> load() {
+        return mMetadataDao().load();
+    }
+
+    /**
+     * Delete one of the {@link Metadata} contains in database
+     *
+     * @param address the address of Metadata to delete
+     */
+    public void delete(String address) {
+        mMetadataDao().delete(address);
+    }
+
+    /**
+     * Clear database.
+     */
+    public void deleteAll() {
+        mMetadataDao().deleteAll();
+    }
+
+    @VisibleForTesting
+    static final Migration MIGRATION_100_101 = new Migration(100, 101) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("ALTER TABLE metadata ADD COLUMN `pbap_client_priority` INTEGER");
+        }
+    };
+
+    @VisibleForTesting
+    static final Migration MIGRATION_101_102 = new Migration(101, 102) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS `metadata_tmp` ("
+                    + "`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, "
+                    + "`a2dpSupportsOptionalCodecs` INTEGER NOT NULL, "
+                    + "`a2dpOptionalCodecsEnabled` INTEGER NOT NULL, "
+                    + "`a2dp_priority` INTEGER, `a2dp_sink_priority` INTEGER, "
+                    + "`hfp_priority` INTEGER, `hfp_client_priority` INTEGER, "
+                    + "`hid_host_priority` INTEGER, `pan_priority` INTEGER, "
+                    + "`pbap_priority` INTEGER, `pbap_client_priority` INTEGER, "
+                    + "`map_priority` INTEGER, `sap_priority` INTEGER, "
+                    + "`hearing_aid_priority` INTEGER, `map_client_priority` INTEGER, "
+                    + "`manufacturer_name` BLOB, `model_name` BLOB, `software_version` BLOB, "
+                    + "`hardware_version` BLOB, `companion_app` BLOB, `main_icon` BLOB, "
+                    + "`is_untethered_headset` BLOB, `untethered_left_icon` BLOB, "
+                    + "`untethered_right_icon` BLOB, `untethered_case_icon` BLOB, "
+                    + "`untethered_left_battery` BLOB, `untethered_right_battery` BLOB, "
+                    + "`untethered_case_battery` BLOB, `untethered_left_charging` BLOB, "
+                    + "`untethered_right_charging` BLOB, `untethered_case_charging` BLOB, "
+                    + "`enhanced_settings_ui_uri` BLOB, PRIMARY KEY(`address`))");
+
+            database.execSQL("INSERT INTO metadata_tmp ("
+                    + "address, migrated, a2dpSupportsOptionalCodecs, a2dpOptionalCodecsEnabled, "
+                    + "a2dp_priority, a2dp_sink_priority, hfp_priority, hfp_client_priority, "
+                    + "hid_host_priority, pan_priority, pbap_priority, pbap_client_priority, "
+                    + "map_priority, sap_priority, hearing_aid_priority, map_client_priority, "
+                    + "manufacturer_name, model_name, software_version, hardware_version, "
+                    + "companion_app, main_icon, is_untethered_headset, untethered_left_icon, "
+                    + "untethered_right_icon, untethered_case_icon, untethered_left_battery, "
+                    + "untethered_right_battery, untethered_case_battery, "
+                    + "untethered_left_charging, untethered_right_charging, "
+                    + "untethered_case_charging, enhanced_settings_ui_uri) "
+                    + "SELECT "
+                    + "address, migrated, a2dpSupportsOptionalCodecs, a2dpOptionalCodecsEnabled, "
+                    + "a2dp_priority, a2dp_sink_priority, hfp_priority, hfp_client_priority, "
+                    + "hid_host_priority, pan_priority, pbap_priority, pbap_client_priority, "
+                    + "map_priority, sap_priority, hearing_aid_priority, map_client_priority, "
+                    + "CAST (manufacturer_name AS BLOB), "
+                    + "CAST (model_name AS BLOB), "
+                    + "CAST (software_version AS BLOB), "
+                    + "CAST (hardware_version AS BLOB), "
+                    + "CAST (companion_app AS BLOB), "
+                    + "CAST (main_icon AS BLOB), "
+                    + "CAST (is_unthethered_headset AS BLOB), "
+                    + "CAST (unthethered_left_icon AS BLOB), "
+                    + "CAST (unthethered_right_icon AS BLOB), "
+                    + "CAST (unthethered_case_icon AS BLOB), "
+                    + "CAST (unthethered_left_battery AS BLOB), "
+                    + "CAST (unthethered_right_battery AS BLOB), "
+                    + "CAST (unthethered_case_battery AS BLOB), "
+                    + "CAST (unthethered_left_charging AS BLOB), "
+                    + "CAST (unthethered_right_charging AS BLOB), "
+                    + "CAST (unthethered_case_charging AS BLOB), "
+                    + "CAST (enhanced_settings_ui_uri AS BLOB)"
+                    + "FROM metadata");
+
+            database.execSQL("DROP TABLE `metadata`");
+            database.execSQL("ALTER TABLE `metadata_tmp` RENAME TO `metadata`");
+        }
+    };
+}
diff --git a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
new file mode 100644
index 0000000..f4ea719
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice.storage;
+
+import android.bluetooth.BluetoothProfile;
+
+import androidx.room.Entity;
+
+@Entity
+class ProfilePrioritiesEntity {
+    /* Bluetooth profile priorities*/
+    public int a2dp_priority;
+    public int a2dp_sink_priority;
+    public int hfp_priority;
+    public int hfp_client_priority;
+    public int hid_host_priority;
+    public int pan_priority;
+    public int pbap_priority;
+    public int pbap_client_priority;
+    public int map_priority;
+    public int sap_priority;
+    public int hearing_aid_priority;
+    public int map_client_priority;
+
+    ProfilePrioritiesEntity() {
+        a2dp_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+        a2dp_sink_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+        hfp_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+        hfp_client_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+        hid_host_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+        pan_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+        pbap_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+        pbap_client_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+        map_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+        sap_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+        hearing_aid_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+        map_client_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+    }
+}
diff --git a/src/com/android/bluetooth/gatt/AppScanStats.java b/src/com/android/bluetooth/gatt/AppScanStats.java
index dd499f2..ec8b1f8 100644
--- a/src/com/android/bluetooth/gatt/AppScanStats.java
+++ b/src/com/android/bluetooth/gatt/AppScanStats.java
@@ -122,16 +122,17 @@
     synchronized void addResult(int scannerId) {
         LastScan scan = getScanFromScannerId(scannerId);
         if (scan != null) {
-            int batteryStatsResults = ++scan.results;
+            scan.results++;
 
             // Only update battery stats after receiving 100 new results in order
             // to lower the cost of the binder transaction
-            if (batteryStatsResults % 100 == 0) {
+            if (scan.results % 100 == 0) {
                 try {
                     mBatteryStats.noteBleScanResults(mWorkSource, 100);
                 } catch (RemoteException e) {
                     /* ignore */
                 }
+                StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, mWorkSource, 100);
             }
         }
 
@@ -178,7 +179,9 @@
         } catch (RemoteException e) {
             /* ignore */
         }
-        writeToStatsLog(scan, StatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON);
+        StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, mWorkSource,
+                StatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON,
+                scan.filtered, scan.background, scan.opportunistic);
 
         mOngoingScans.put(scannerId, scan);
     }
@@ -221,15 +224,17 @@
         }
 
         try {
-            // Inform battery stats of any results it might be missing on
-            // scan stop
+            // Inform battery stats of any results it might be missing on scan stop
             boolean isUnoptimized = !(scan.filtered || scan.background || scan.opportunistic);
             mBatteryStats.noteBleScanResults(mWorkSource, scan.results % 100);
             mBatteryStats.noteBleScanStopped(mWorkSource, isUnoptimized);
         } catch (RemoteException e) {
             /* ignore */
         }
-        writeToStatsLog(scan, StatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF);
+        StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, mWorkSource, scan.results % 100);
+        StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, mWorkSource,
+                StatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF,
+                scan.filtered, scan.background, scan.opportunistic);
     }
 
     synchronized void recordScanSuspend(int scannerId) {
@@ -254,24 +259,6 @@
         mTotalSuspendTime += suspendDuration;
     }
 
-    private void writeToStatsLog(LastScan scan, int statsLogState) {
-        for (int i = 0; i < mWorkSource.size(); i++) {
-            StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED,
-                    mWorkSource.get(i), null,
-                    statsLogState, scan.filtered, scan.background, scan.opportunistic);
-        }
-
-        final List<WorkSource.WorkChain> workChains = mWorkSource.getWorkChains();
-        if (workChains != null) {
-            for (int i = 0; i < workChains.size(); ++i) {
-                WorkSource.WorkChain workChain = workChains.get(i);
-                StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED,
-                        workChain.getUids(), workChain.getTags(),
-                        statsLogState, scan.filtered, scan.background, scan.opportunistic);
-            }
-        }
-    }
-
     synchronized void setScanTimeout(int scannerId) {
         if (!isScanning()) {
             return;
diff --git a/src/com/android/bluetooth/gatt/ContextMap.java b/src/com/android/bluetooth/gatt/ContextMap.java
index af59262..d54a25a 100644
--- a/src/com/android/bluetooth/gatt/ContextMap.java
+++ b/src/com/android/bluetooth/gatt/ContextMap.java
@@ -20,6 +20,7 @@
 import android.os.IInterface;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.Log;
 
@@ -86,10 +87,22 @@
         public Boolean isCongested = false;
 
         /** Whether the calling app has location permission */
-        boolean hasLocationPermisson;
+        boolean hasLocationPermission;
 
-        /** Whether the calling app has peers mac address permission */
-        boolean hasPeersMacAddressPermission;
+        /** Whether the calling app has bluetooth privileged permission */
+        boolean hasBluetoothPrivilegedPermission;
+
+        /** The user handle of the app that started the scan */
+        UserHandle mUserHandle;
+
+        /** Whether the calling app is targeting Q or better */
+        boolean mIsQApp;
+
+        /** Whether the calling app has the network settings permission */
+        boolean mHasNetworkSettingsPermission;
+
+        /** Whether the calling app has the network setup wizard permission */
+        boolean mHasNetworkSetupWizardPermission;
 
         /** Internal callback info queue, waiting to be send on congestion clear */
         private List<CallbackInfo> mCongestionQueue = new ArrayList<CallbackInfo>();
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index fd8551a..8a3a1aa 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -50,8 +50,8 @@
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.os.WorkSource;
-import android.provider.Settings;
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothMetricsProto;
@@ -63,6 +63,7 @@
 import com.android.bluetooth.util.NumberUtils;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -98,6 +99,9 @@
 
     private static final int ET_LEGACY_MASK = 0x10;
 
+    private static final UUID HID_SERVICE_UUID =
+            UUID.fromString("00001812-0000-1000-8000-00805F9B34FB");
+
     private static final UUID[] HID_UUIDS = {
             UUID.fromString("00002A4A-0000-1000-8000-00805F9B34FB"),
             UUID.fromString("00002A4B-0000-1000-8000-00805F9B34FB"),
@@ -105,9 +109,11 @@
             UUID.fromString("00002A4D-0000-1000-8000-00805F9B34FB")
     };
 
-    private static final UUID[] FIDO_UUIDS = {
-            UUID.fromString("0000FFFD-0000-1000-8000-00805F9B34FB") // U2F
-    };
+    private static final UUID ANDROID_TV_REMOTE_SERVICE_UUID =
+            UUID.fromString("AB5E0001-5A21-4F05-BC7D-AF01F617B664");
+
+    private static final UUID FIDO_SERVICE_UUID =
+            UUID.fromString("0000FFFD-0000-1000-8000-00805F9B34FB"); // U2F
 
     /**
      * Keep the arguments passed in for the PendingIntent.
@@ -157,13 +163,17 @@
     private int mMaxScanFilters;
 
     private static final int NUM_SCAN_EVENTS_KEPT = 20;
+
     /**
      * Internal list of scan events to use with the proto
      */
-    private final ArrayList<BluetoothMetricsProto.ScanEvent> mScanEvents =
-            new ArrayList<>(NUM_SCAN_EVENTS_KEPT);
+    private final ArrayDeque<BluetoothMetricsProto.ScanEvent> mScanEvents =
+            new ArrayDeque<>(NUM_SCAN_EVENTS_KEPT);
 
-    private final Map<Integer, List<BluetoothGattService>> mGattClientDatabases = new HashMap<>();
+    /**
+     * Set of restricted (which require a BLUETOOTH_PRIVILEGED permission) handles per connectionId.
+     */
+    private final Map<Integer, Set<Integer>> mRestrictedHandles = new HashMap<>();
 
     private BluetoothAdapter mAdapter;
     private AdvertiseManager mAdvertiseManager;
@@ -274,36 +284,34 @@
         sGattService = instance;
     }
 
-    boolean permissionCheck(UUID uuid) {
-        return !(isRestrictedCharUuid(uuid) && (0 != checkCallingOrSelfPermission(
-                BLUETOOTH_PRIVILEGED)));
+    private boolean permissionCheck(UUID characteristicUuid) {
+        return !isHidCharUuid(characteristicUuid)
+                || (checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)
+                        == PERMISSION_GRANTED);
     }
 
-    boolean permissionCheck(int connId, int handle) {
-        List<BluetoothGattService> db = mGattClientDatabases.get(connId);
-        if (db == null) {
+    private boolean permissionCheck(int connId, int handle) {
+        Set<Integer> restrictedHandles = mRestrictedHandles.get(connId);
+        if (restrictedHandles == null || !restrictedHandles.contains(handle)) {
             return true;
         }
 
-        for (BluetoothGattService service : db) {
-            for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
-                if (handle == characteristic.getInstanceId()) {
-                    return !((isRestrictedCharUuid(characteristic.getUuid())
-                            || isRestrictedSrvcUuid(service.getUuid()))
-                            && (0 != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)));
-                }
+        return (checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)
+                == PERMISSION_GRANTED);
+    }
 
-                for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
-                    if (handle == descriptor.getInstanceId()) {
-                        return !((isRestrictedCharUuid(characteristic.getUuid())
-                                || isRestrictedSrvcUuid(service.getUuid())) && (0
-                                != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)));
-                    }
-                }
-            }
+    private boolean permissionCheck(ClientMap.App app, int connId, int handle) {
+        Set<Integer> restrictedHandles = mRestrictedHandles.get(connId);
+        if (restrictedHandles == null || !restrictedHandles.contains(handle)) {
+            return true;
         }
 
-        return true;
+        if (!app.hasBluetoothPrivilegedPermission
+                && checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)== PERMISSION_GRANTED) {
+            app.hasBluetoothPrivilegedPermission = true;
+        }
+
+        return app.hasBluetoothPrivilegedPermission;
     }
 
     @Override
@@ -1000,8 +1008,7 @@
                             txPower, rssi, periodicAdvInt,
                             ScanRecord.parseFromBytes(scanRecordData),
                             SystemClock.elapsedRealtimeNanos());
-            // Do no report if location mode is OFF or the client has no location permission
-            // PEERS_MAC_ADDRESS permission holders always get results
+            // Do not report if location mode is OFF or the client has no location permission
             if (!hasScanResultPermission(client) || !matchesFilters(client, result)) {
                 continue;
             }
@@ -1088,15 +1095,10 @@
 
     /** Determines if the given scan client has the appropriate permissions to receive callbacks. */
     private boolean hasScanResultPermission(final ScanClient client) {
-        final boolean requiresLocationEnabled =
-                getResources().getBoolean(R.bool.strict_location_check);
-        final boolean locationEnabledSetting =
-                Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE,
-                        Settings.Secure.LOCATION_MODE_OFF) != Settings.Secure.LOCATION_MODE_OFF;
-        final boolean locationEnabled =
-                !requiresLocationEnabled || locationEnabledSetting || client.legacyForegroundApp;
-        return (client.hasPeersMacAddressPermission || (client.hasLocationPermission
-                && locationEnabled));
+        if (client.hasNetworkSettingsPermission || client.hasNetworkSetupWizardPermission) {
+            return true;
+        }
+        return client.hasLocationPermission && !Utils.blockedByLocationOff(this, client.userHandle);
     }
 
     // Check if a scan record matches a specific filters.
@@ -1312,9 +1314,13 @@
         }
 
         List<BluetoothGattService> dbOut = new ArrayList<BluetoothGattService>();
+        Set<Integer> restrictedIds = new HashSet<>();
 
         BluetoothGattService currSrvc = null;
         BluetoothGattCharacteristic currChar = null;
+        boolean isRestrictedSrvc = false;
+        boolean isHidSrvc = false;
+        boolean isRestrictedChar = false;
 
         for (GattDbElement el : db) {
             switch (el.type) {
@@ -1326,6 +1332,12 @@
 
                     currSrvc = new BluetoothGattService(el.uuid, el.id, el.type);
                     dbOut.add(currSrvc);
+                    isRestrictedSrvc =
+                            isFidoSrvcUuid(el.uuid) || isAndroidTvRemoteSrvcUuid(el.uuid);
+                    isHidSrvc = isHidSrvcUuid(el.uuid);
+                    if (isRestrictedSrvc) {
+                        restrictedIds.add(el.id);
+                    }
                     break;
 
                 case GattDbElement.TYPE_CHARACTERISTIC:
@@ -1335,6 +1347,10 @@
 
                     currChar = new BluetoothGattCharacteristic(el.uuid, el.id, el.properties, 0);
                     currSrvc.addCharacteristic(currChar);
+                    isRestrictedChar = isRestrictedSrvc || (isHidSrvc && isHidCharUuid(el.uuid));
+                    if (isRestrictedChar) {
+                        restrictedIds.add(el.id);
+                    }
                     break;
 
                 case GattDbElement.TYPE_DESCRIPTOR:
@@ -1343,6 +1359,9 @@
                     }
 
                     currChar.addDescriptor(new BluetoothGattDescriptor(el.uuid, el.id, 0));
+                    if (isRestrictedChar) {
+                        restrictedIds.add(el.id);
+                    }
                     break;
 
                 case GattDbElement.TYPE_INCLUDED_SERVICE:
@@ -1361,8 +1380,10 @@
             }
         }
 
+        if (!restrictedIds.isEmpty()) {
+            mRestrictedHandles.put(connId, restrictedIds);
+        }
         // Search is complete when there was error, or nothing more to process
-        mGattClientDatabases.put(connId, dbOut);
         app.callback.onSearchComplete(address, dbOut, 0 /* status */);
     }
 
@@ -1383,13 +1404,12 @@
                     + data.length);
         }
 
-        if (!permissionCheck(connId, handle)) {
-            Log.w(TAG, "onNotify() - permission check failed!");
-            return;
-        }
-
         ClientMap.App app = mClientMap.getByConnId(connId);
         if (app != null) {
+            if (!permissionCheck(app, connId, handle)) {
+                Log.w(TAG, "onNotify() - permission check failed!");
+                return;
+            }
             app.callback.onNotify(address, handle, data);
         }
     }
@@ -1917,16 +1937,28 @@
         if (DBG) {
             Log.d(TAG, "start scan with filters");
         }
+        UserHandle callingUser = UserHandle.of(UserHandle.getCallingUserId());
         enforceAdminPermission();
         if (needsPrivilegedPermissionForScan(settings)) {
             enforcePrivilegedPermission();
         }
         final ScanClient scanClient = new ScanClient(scannerId, settings, filters, storages);
-        scanClient.hasLocationPermission =
-                Utils.checkCallerHasLocationPermission(this, mAppOps, callingPackage);
-        scanClient.hasPeersMacAddressPermission =
-                Utils.checkCallerHasPeersMacAddressPermission(this);
-        scanClient.legacyForegroundApp = Utils.isLegacyForegroundApp(this, callingPackage);
+        scanClient.userHandle = UserHandle.of(UserHandle.getCallingUserId());
+        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+        scanClient.isQApp = Utils.isQApp(this, callingPackage);
+        if (scanClient.isQApp) {
+            scanClient.hasLocationPermission =
+                    Utils.checkCallerHasFineLocation(
+                            this, mAppOps, callingPackage, scanClient.userHandle);
+        } else {
+            scanClient.hasLocationPermission =
+                    Utils.checkCallerHasCoarseOrFineLocation(
+                            this, mAppOps, callingPackage, scanClient.userHandle);
+        }
+        scanClient.hasNetworkSettingsPermission =
+                Utils.checkCallerHasNetworkSettingsPermission(this);
+        scanClient.hasNetworkSetupWizardPermission =
+                Utils.checkCallerHasNetworkSetupWizardPermission(this);
 
         AppScanStats app = mScannerMap.getAppScanStatsById(scannerId);
         if (app != null) {
@@ -1958,19 +1990,25 @@
         piInfo.filters = filters;
         piInfo.callingPackage = callingPackage;
         ScannerMap.App app = mScannerMap.add(uuid, null, null, piInfo, this);
+        app.mUserHandle = UserHandle.of(UserHandle.getCallingUserId());
+        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+        app.mIsQApp = Utils.isQApp(this, callingPackage);
         try {
-            app.hasLocationPermisson =
-                    Utils.checkCallerHasLocationPermission(this, mAppOps, callingPackage);
+            if (app.mIsQApp) {
+                app.hasLocationPermission = Utils.checkCallerHasFineLocation(
+                      this, mAppOps, callingPackage, app.mUserHandle);
+            } else {
+                app.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(
+                      this, mAppOps, callingPackage, app.mUserHandle);
+            }
         } catch (SecurityException se) {
             // No need to throw here. Just mark as not granted.
-            app.hasLocationPermisson = false;
+            app.hasLocationPermission = false;
         }
-        try {
-            app.hasPeersMacAddressPermission = Utils.checkCallerHasPeersMacAddressPermission(this);
-        } catch (SecurityException se) {
-            // No need to throw here. Just mark as not granted.
-            app.hasPeersMacAddressPermission = false;
-        }
+        app.mHasNetworkSettingsPermission =
+                Utils.checkCallerHasNetworkSettingsPermission(this);
+        app.mHasNetworkSetupWizardPermission =
+                Utils.checkCallerHasNetworkSetupWizardPermission(this);
         mScanManager.registerScanner(uuid);
     }
 
@@ -1978,9 +2016,11 @@
         final PendingIntentInfo piInfo = app.info;
         final ScanClient scanClient =
                 new ScanClient(scannerId, piInfo.settings, piInfo.filters, null);
-        scanClient.hasLocationPermission = app.hasLocationPermisson;
-        scanClient.hasPeersMacAddressPermission = app.hasPeersMacAddressPermission;
-        scanClient.legacyForegroundApp = Utils.isLegacyForegroundApp(this, piInfo.callingPackage);
+        scanClient.hasLocationPermission = app.hasLocationPermission;
+        scanClient.userHandle = app.mUserHandle;
+        scanClient.isQApp = app.mIsQApp;
+        scanClient.hasNetworkSettingsPermission = app.mHasNetworkSettingsPermission;
+        scanClient.hasNetworkSetupWizardPermission = app.mHasNetworkSetupWizardPermission;
 
         AppScanStats scanStats = mScannerMap.getAppScanStatsById(scannerId);
         if (scanStats != null) {
@@ -2457,7 +2497,7 @@
         int latency;
 
         // Link supervision timeout is measured in N * 10ms
-        int timeout = 2000; // 20s
+        int timeout = 500; // 5s
 
         switch (connectionPriority) {
             case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
@@ -2978,15 +3018,11 @@
      * Private functions
      *************************************************************************/
 
-    private boolean isRestrictedCharUuid(final UUID charUuid) {
-        return isHidUuid(charUuid);
+    private boolean isHidSrvcUuid(final UUID uuid) {
+        return HID_SERVICE_UUID.equals(uuid);
     }
 
-    private boolean isRestrictedSrvcUuid(final UUID srvcUuid) {
-        return isFidoUUID(srvcUuid);
-    }
-
-    private boolean isHidUuid(final UUID uuid) {
+    private boolean isHidCharUuid(final UUID uuid) {
         for (UUID hidUuid : HID_UUIDS) {
             if (hidUuid.equals(uuid)) {
                 return true;
@@ -2995,13 +3031,12 @@
         return false;
     }
 
-    private boolean isFidoUUID(final UUID uuid) {
-        for (UUID fidoUuid : FIDO_UUIDS) {
-            if (fidoUuid.equals(uuid)) {
-                return true;
-            }
-        }
-        return false;
+    private boolean isAndroidTvRemoteSrvcUuid(final UUID uuid) {
+        return ANDROID_TV_REMOTE_SERVICE_UUID.equals(uuid);
+    }
+
+    private boolean isFidoSrvcUuid(final UUID uuid) {
+        return FIDO_SERVICE_UUID.equals(uuid);
     }
 
     private int getDeviceType(BluetoothDevice device) {
@@ -3153,7 +3188,7 @@
     void addScanEvent(BluetoothMetricsProto.ScanEvent event) {
         synchronized (mScanEvents) {
             if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT) {
-                mScanEvents.remove(0);
+                mScanEvents.remove();
             }
             mScanEvents.add(event);
         }
diff --git a/src/com/android/bluetooth/gatt/ScanClient.java b/src/com/android/bluetooth/gatt/ScanClient.java
index f774347..83a034b 100644
--- a/src/com/android/bluetooth/gatt/ScanClient.java
+++ b/src/com/android/bluetooth/gatt/ScanClient.java
@@ -20,6 +20,7 @@
 import android.bluetooth.le.ScanFilter;
 import android.bluetooth.le.ScanSettings;
 import android.os.Binder;
+import android.os.UserHandle;
 
 import java.util.List;
 import java.util.Objects;
@@ -41,9 +42,10 @@
     // App associated with the scan client died.
     public boolean appDied;
     public boolean hasLocationPermission;
-    public boolean hasPeersMacAddressPermission;
-    // Pre-M apps are allowed to get scan results even if location is disabled
-    public boolean legacyForegroundApp;
+    public UserHandle userHandle;
+    public boolean isQApp;
+    public boolean hasNetworkSettingsPermission;
+    public boolean hasNetworkSetupWizardPermission;
 
     public AppScanStats stats = null;
 
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..0736978 100644
--- a/src/com/android/bluetooth/gatt/ScanManager.java
+++ b/src/com/android/bluetooth/gatt/ScanManager.java
@@ -323,7 +323,7 @@
             }
 
             final boolean locationEnabled = mLocationManager.isLocationEnabled();
-            if (!locationEnabled && !isFiltered && !client.legacyForegroundApp) {
+            if (!locationEnabled && !isFiltered) {
                 Log.i(TAG, "Cannot start unfiltered scan in location-off. This scan will be"
                         + " resumed when location is on: " + client.scannerId);
                 mSuspendedScanClients.add(client);
@@ -416,7 +416,7 @@
         void handleSuspendScans() {
             for (ScanClient client : mRegularScanClients) {
                 if (!mScanNative.isOpportunisticScanClient(client) && (client.filters == null
-                        || client.filters.isEmpty()) && !client.legacyForegroundApp) {
+                        || client.filters.isEmpty())) {
                     /*Suspend unfiltered scans*/
                     if (client.stats != null) {
                         client.stats.recordScanSuspend(client.scannerId);
diff --git a/src/com/android/bluetooth/hdp/HealthService.java b/src/com/android/bluetooth/hdp/HealthService.java
deleted file mode 100644
index 6a4af04..0000000
--- a/src/com/android/bluetooth/hdp/HealthService.java
+++ /dev/null
@@ -1,1014 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.hdp;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHealth;
-import android.bluetooth.BluetoothHealthAppConfiguration;
-import android.bluetooth.IBluetoothHealth;
-import android.bluetooth.IBluetoothHealthCallback;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.support.annotation.VisibleForTesting;
-import android.util.Log;
-
-import com.android.bluetooth.BluetoothMetricsProto;
-import com.android.bluetooth.Utils;
-import com.android.bluetooth.btservice.MetricsLogger;
-import com.android.bluetooth.btservice.ProfileService;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.NoSuchElementException;
-
-
-/**
- * Provides Bluetooth Health Device profile, as a service in
- * the Bluetooth application.
- * @hide
- */
-public class HealthService extends ProfileService {
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
-    private static final String TAG = "HealthService";
-
-    private List<HealthChannel> mHealthChannels;
-    private Map<BluetoothHealthAppConfiguration, AppInfo> mApps;
-    private Map<BluetoothDevice, Integer> mHealthDevices;
-    private boolean mNativeAvailable;
-    private HealthServiceMessageHandler mHandler;
-    private static final int MESSAGE_REGISTER_APPLICATION = 1;
-    private static final int MESSAGE_UNREGISTER_APPLICATION = 2;
-    private static final int MESSAGE_CONNECT_CHANNEL = 3;
-    private static final int MESSAGE_DISCONNECT_CHANNEL = 4;
-    private static final int MESSAGE_APP_REGISTRATION_CALLBACK = 11;
-    private static final int MESSAGE_CHANNEL_STATE_CALLBACK = 12;
-
-    private static HealthService sHealthService;
-
-    static {
-        classInitNative();
-    }
-
-    @Override
-    protected IProfileServiceBinder initBinder() {
-        return new BluetoothHealthBinder(this);
-    }
-
-    @Override
-    protected boolean start() {
-        mHealthChannels = Collections.synchronizedList(new ArrayList<HealthChannel>());
-        mApps = Collections.synchronizedMap(
-                new HashMap<BluetoothHealthAppConfiguration, AppInfo>());
-        mHealthDevices = Collections.synchronizedMap(new HashMap<BluetoothDevice, Integer>());
-
-        HandlerThread thread = new HandlerThread("BluetoothHdpHandler");
-        thread.start();
-        Looper looper = thread.getLooper();
-        mHandler = new HealthServiceMessageHandler(looper);
-        initializeNative();
-        mNativeAvailable = true;
-        setHealthService(this);
-        return true;
-    }
-
-    @Override
-    protected boolean stop() {
-        setHealthService(null);
-        if (mHandler != null) {
-            mHandler.removeCallbacksAndMessages(null);
-            Looper looper = mHandler.getLooper();
-            if (looper != null) {
-                looper.quit();
-            }
-        }
-        cleanupApps();
-        return true;
-    }
-
-    private void cleanupApps() {
-        if (mApps != null) {
-            Iterator<Map.Entry<BluetoothHealthAppConfiguration, AppInfo>> it =
-                    mApps.entrySet().iterator();
-            while (it.hasNext()) {
-                Map.Entry<BluetoothHealthAppConfiguration, AppInfo> entry = it.next();
-                AppInfo appInfo = entry.getValue();
-                if (appInfo != null) {
-                    appInfo.cleanup();
-                }
-                it.remove();
-            }
-        }
-    }
-
-    @Override
-    protected void cleanup() {
-        mHandler = null;
-        //Cleanup native
-        if (mNativeAvailable) {
-            cleanupNative();
-            mNativeAvailable = false;
-        }
-        if (mHealthChannels != null) {
-            mHealthChannels.clear();
-        }
-        if (mHealthDevices != null) {
-            mHealthDevices.clear();
-        }
-        if (mApps != null) {
-            mApps.clear();
-        }
-    }
-
-    /**
-     * Get a static reference to the current health service instance
-     *
-     * @return current health service instance
-     */
-    @VisibleForTesting
-    public static synchronized HealthService getHealthService() {
-        if (sHealthService == null) {
-            Log.w(TAG, "getHealthService(): service is null");
-            return null;
-        }
-        if (!sHealthService.isAvailable()) {
-            Log.w(TAG, "getHealthService(): service is not available");
-            return null;
-        }
-        return sHealthService;
-    }
-
-    private static synchronized void setHealthService(HealthService instance) {
-        if (DBG) {
-            Log.d(TAG, "setHealthService(): set to: " + instance);
-        }
-        sHealthService = instance;
-    }
-
-    private final class HealthServiceMessageHandler extends Handler {
-        private HealthServiceMessageHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (DBG) {
-                Log.d(TAG, "HealthService Handler msg: " + msg.what);
-            }
-            switch (msg.what) {
-                case MESSAGE_REGISTER_APPLICATION: {
-                    BluetoothHealthAppConfiguration appConfig =
-                            (BluetoothHealthAppConfiguration) msg.obj;
-                    AppInfo appInfo = mApps.get(appConfig);
-                    if (appInfo == null) {
-                        break;
-                    }
-                    int halRole = convertRoleToHal(appConfig.getRole());
-                    int halChannelType = convertChannelTypeToHal(appConfig.getChannelType());
-                    if (VDBG) {
-                        Log.d(TAG, "register datatype: " + appConfig.getDataType() + " role: "
-                                + halRole + " name: " + appConfig.getName() + " channeltype: "
-                                + halChannelType);
-                    }
-                    int appId = registerHealthAppNative(appConfig.getDataType(), halRole,
-                            appConfig.getName(), halChannelType);
-                    if (appId == -1) {
-                        callStatusCallback(appConfig,
-                                BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE);
-                        appInfo.cleanup();
-                        mApps.remove(appConfig);
-                    } else {
-                        //link to death with a recipient object to implement binderDead()
-                        appInfo.mRcpObj =
-                                new BluetoothHealthDeathRecipient(HealthService.this, appConfig);
-                        IBinder binder = appInfo.mCallback.asBinder();
-                        try {
-                            binder.linkToDeath(appInfo.mRcpObj, 0);
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "LinktoDeath Exception:" + e);
-                        }
-                        appInfo.mAppId = appId;
-                        callStatusCallback(appConfig,
-                                BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS);
-                    }
-                }
-                break;
-                case MESSAGE_UNREGISTER_APPLICATION: {
-                    BluetoothHealthAppConfiguration appConfig =
-                            (BluetoothHealthAppConfiguration) msg.obj;
-                    int appId = (mApps.get(appConfig)).mAppId;
-                    if (!unregisterHealthAppNative(appId)) {
-                        Log.e(TAG, "Failed to unregister application: id: " + appId);
-                        callStatusCallback(appConfig,
-                                BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE);
-                    }
-                }
-                break;
-                case MESSAGE_CONNECT_CHANNEL: {
-                    HealthChannel chan = (HealthChannel) msg.obj;
-                    byte[] devAddr = Utils.getByteAddress(chan.mDevice);
-                    int appId = (mApps.get(chan.mConfig)).mAppId;
-                    chan.mChannelId = connectChannelNative(devAddr, appId);
-                    if (chan.mChannelId == -1) {
-                        callHealthChannelCallback(chan.mConfig, chan.mDevice,
-                                BluetoothHealth.STATE_CHANNEL_DISCONNECTING,
-                                BluetoothHealth.STATE_CHANNEL_DISCONNECTED, chan.mChannelFd,
-                                chan.mChannelId);
-                        callHealthChannelCallback(chan.mConfig, chan.mDevice,
-                                BluetoothHealth.STATE_CHANNEL_DISCONNECTED,
-                                BluetoothHealth.STATE_CHANNEL_DISCONNECTING, chan.mChannelFd,
-                                chan.mChannelId);
-                    }
-                }
-                break;
-                case MESSAGE_DISCONNECT_CHANNEL: {
-                    HealthChannel chan = (HealthChannel) msg.obj;
-                    if (!disconnectChannelNative(chan.mChannelId)) {
-                        callHealthChannelCallback(chan.mConfig, chan.mDevice,
-                                BluetoothHealth.STATE_CHANNEL_DISCONNECTING,
-                                BluetoothHealth.STATE_CHANNEL_CONNECTED, chan.mChannelFd,
-                                chan.mChannelId);
-                        callHealthChannelCallback(chan.mConfig, chan.mDevice,
-                                BluetoothHealth.STATE_CHANNEL_CONNECTED,
-                                BluetoothHealth.STATE_CHANNEL_DISCONNECTING, chan.mChannelFd,
-                                chan.mChannelId);
-                    }
-                }
-                break;
-                case MESSAGE_APP_REGISTRATION_CALLBACK: {
-                    BluetoothHealthAppConfiguration appConfig = findAppConfigByAppId(msg.arg1);
-                    if (appConfig == null) {
-                        break;
-                    }
-
-                    int regStatus = convertHalRegStatus(msg.arg2);
-                    callStatusCallback(appConfig, regStatus);
-                    if (regStatus == BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE
-                            || regStatus == BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS) {
-                        //unlink to death once app is unregistered
-                        AppInfo appInfo = mApps.get(appConfig);
-                        appInfo.cleanup();
-                        mApps.remove(appConfig);
-                    }
-                }
-                break;
-                case MESSAGE_CHANNEL_STATE_CALLBACK: {
-                    ChannelStateEvent channelStateEvent = (ChannelStateEvent) msg.obj;
-                    HealthChannel chan = findChannelById(channelStateEvent.mChannelId);
-                    BluetoothHealthAppConfiguration appConfig =
-                            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");
-                        break;
-                    }
-                    if (chan == null) {
-                        // incoming connection
-
-                        BluetoothDevice device = getDevice(channelStateEvent.mAddr);
-                        chan = new HealthChannel(device, appConfig, appConfig.getChannelType());
-                        chan.mChannelId = channelStateEvent.mChannelId;
-                        mHealthChannels.add(chan);
-                    }
-                    newState = convertHalChannelState(channelStateEvent.mState);
-                    if (newState == BluetoothHealth.STATE_CHANNEL_CONNECTED) {
-                        try {
-                            chan.mChannelFd = ParcelFileDescriptor.dup(channelStateEvent.mFd);
-                        } catch (IOException e) {
-                            Log.e(TAG, "failed to dup ParcelFileDescriptor");
-                            break;
-                        }
-                    } else {
-                        /*set the channel fd to null if channel state isnot equal to connected*/
-                        chan.mChannelFd = null;
-                    }
-                    callHealthChannelCallback(chan.mConfig, chan.mDevice, newState, chan.mState,
-                            chan.mChannelFd, chan.mChannelId);
-                    chan.mState = newState;
-                    if (channelStateEvent.mState == CONN_STATE_DESTROYED) {
-                        mHealthChannels.remove(chan);
-                    }
-                }
-                break;
-            }
-        }
-    }
-
-    //Handler for DeathReceipient
-    private static class BluetoothHealthDeathRecipient implements IBinder.DeathRecipient {
-        private BluetoothHealthAppConfiguration mConfig;
-        private HealthService mService;
-
-        BluetoothHealthDeathRecipient(HealthService service,
-                BluetoothHealthAppConfiguration config) {
-            mService = service;
-            mConfig = config;
-        }
-
-        @Override
-        public void binderDied() {
-            if (DBG) {
-                Log.d(TAG, "Binder is dead.");
-            }
-            mService.unregisterAppConfiguration(mConfig);
-        }
-
-        public void cleanup() {
-            mService = null;
-            mConfig = null;
-        }
-    }
-
-    /**
-     * Handlers for incoming service calls
-     */
-    private static class BluetoothHealthBinder extends IBluetoothHealth.Stub
-            implements IProfileServiceBinder {
-        private HealthService mService;
-
-        BluetoothHealthBinder(HealthService svc) {
-            mService = svc;
-        }
-
-        @Override
-        public void cleanup() {
-            mService = null;
-        }
-
-        private HealthService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "Health call not allowed for non-active user");
-                return null;
-            }
-
-            if (mService != null && mService.isAvailable()) {
-                return mService;
-            }
-            return null;
-        }
-
-        @Override
-        public boolean registerAppConfiguration(BluetoothHealthAppConfiguration config,
-                IBluetoothHealthCallback callback) {
-            HealthService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.registerAppConfiguration(config, callback);
-        }
-
-        @Override
-        public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
-            HealthService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.unregisterAppConfiguration(config);
-        }
-
-        @Override
-        public boolean connectChannelToSource(BluetoothDevice device,
-                BluetoothHealthAppConfiguration config) {
-            HealthService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.connectChannelToSource(device, config);
-        }
-
-        @Override
-        public boolean connectChannelToSink(BluetoothDevice device,
-                BluetoothHealthAppConfiguration config, int channelType) {
-            HealthService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.connectChannelToSink(device, config, channelType);
-        }
-
-        @Override
-        public boolean disconnectChannel(BluetoothDevice device,
-                BluetoothHealthAppConfiguration config, int channelId) {
-            HealthService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.disconnectChannel(device, config, channelId);
-        }
-
-        @Override
-        public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
-                BluetoothHealthAppConfiguration config) {
-            HealthService service = getService();
-            if (service == null) {
-                return null;
-            }
-            return service.getMainChannelFd(device, config);
-        }
-
-        @Override
-        public int getHealthDeviceConnectionState(BluetoothDevice device) {
-            HealthService service = getService();
-            if (service == null) {
-                return BluetoothHealth.STATE_DISCONNECTED;
-            }
-            return service.getHealthDeviceConnectionState(device);
-        }
-
-        @Override
-        public List<BluetoothDevice> getConnectedHealthDevices() {
-            HealthService service = getService();
-            if (service == null) {
-                return new ArrayList<BluetoothDevice>(0);
-            }
-            return service.getConnectedHealthDevices();
-        }
-
-        @Override
-        public List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) {
-            HealthService service = getService();
-            if (service == null) {
-                return new ArrayList<BluetoothDevice>(0);
-            }
-            return service.getHealthDevicesMatchingConnectionStates(states);
-        }
-    }
-
-    ;
-
-    boolean registerAppConfiguration(BluetoothHealthAppConfiguration config,
-            IBluetoothHealthCallback callback) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        if (config == null) {
-            Log.e(TAG, "Trying to use a null config for registration");
-            return false;
-        }
-
-        if (mApps.get(config) != null) {
-            if (DBG) {
-                Log.d(TAG, "Config has already been registered");
-            }
-            return false;
-        }
-        mApps.put(config, new AppInfo(callback));
-        Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_APPLICATION, config);
-        mHandler.sendMessage(msg);
-        return true;
-    }
-
-    boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (mApps.get(config) == null) {
-            if (DBG) {
-                Log.d(TAG, "unregisterAppConfiguration: no app found");
-            }
-            return false;
-        }
-        Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION, config);
-        mHandler.sendMessage(msg);
-        return true;
-    }
-
-    boolean connectChannelToSource(BluetoothDevice device, BluetoothHealthAppConfiguration config) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return connectChannel(device, config, BluetoothHealth.CHANNEL_TYPE_ANY);
-    }
-
-    boolean connectChannelToSink(BluetoothDevice device, BluetoothHealthAppConfiguration config,
-            int channelType) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return connectChannel(device, config, channelType);
-    }
-
-    boolean disconnectChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config,
-            int channelId) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        HealthChannel chan = findChannelById(channelId);
-        if (chan == null) {
-            if (DBG) {
-                Log.d(TAG, "disconnectChannel: no channel found");
-            }
-            return false;
-        }
-        Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT_CHANNEL, chan);
-        mHandler.sendMessage(msg);
-        return true;
-    }
-
-    ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
-            BluetoothHealthAppConfiguration config) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        HealthChannel healthChan = null;
-        for (HealthChannel chan : mHealthChannels) {
-            if (chan.mDevice.equals(device) && chan.mConfig.equals(config)) {
-                healthChan = chan;
-            }
-        }
-        if (healthChan == null) {
-            Log.e(TAG, "No channel found for device: " + device + " config: " + config);
-            return null;
-        }
-        return healthChan.mChannelFd;
-    }
-
-    int getHealthDeviceConnectionState(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return getConnectionState(device);
-    }
-
-    List<BluetoothDevice> getConnectedHealthDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        List<BluetoothDevice> devices =
-                lookupHealthDevicesMatchingStates(new int[]{BluetoothHealth.STATE_CONNECTED});
-        return devices;
-    }
-
-    List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states);
-        return devices;
-    }
-
-    private void onAppRegistrationState(int appId, int state) {
-        Message msg = mHandler.obtainMessage(MESSAGE_APP_REGISTRATION_CALLBACK);
-        msg.arg1 = appId;
-        msg.arg2 = state;
-        mHandler.sendMessage(msg);
-    }
-
-    private void onChannelStateChanged(int appId, byte[] addr, int cfgIndex, int channelId,
-            int state, FileDescriptor pfd) {
-        Message msg = mHandler.obtainMessage(MESSAGE_CHANNEL_STATE_CALLBACK);
-        ChannelStateEvent channelStateEvent =
-                new ChannelStateEvent(appId, addr, cfgIndex, channelId, state, pfd);
-        msg.obj = channelStateEvent;
-        mHandler.sendMessage(msg);
-    }
-
-    private String getStringChannelType(int type) {
-        if (type == BluetoothHealth.CHANNEL_TYPE_RELIABLE) {
-            return "Reliable";
-        } else if (type == BluetoothHealth.CHANNEL_TYPE_STREAMING) {
-            return "Streaming";
-        } else {
-            return "Any";
-        }
-    }
-
-    private void callStatusCallback(BluetoothHealthAppConfiguration config, int status) {
-        if (VDBG) {
-            Log.d(TAG, "Health Device Application: " + config + " State Change: status:" + status);
-        }
-        IBluetoothHealthCallback callback = (mApps.get(config)).mCallback;
-        if (callback == null) {
-            Log.e(TAG, "Callback object null");
-        }
-
-        try {
-            callback.onHealthAppConfigurationStatusChange(config, status);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Remote Exception:" + e);
-        }
-    }
-
-    private BluetoothHealthAppConfiguration findAppConfigByAppId(int appId) {
-        BluetoothHealthAppConfiguration appConfig = null;
-        for (Entry<BluetoothHealthAppConfiguration, AppInfo> e : mApps.entrySet()) {
-            if (appId == (e.getValue()).mAppId) {
-                appConfig = e.getKey();
-                break;
-            }
-        }
-        if (appConfig == null) {
-            Log.e(TAG, "No appConfig found for " + appId);
-        }
-        return appConfig;
-    }
-
-    private int convertHalRegStatus(int halRegStatus) {
-        switch (halRegStatus) {
-            case APP_REG_STATE_REG_SUCCESS:
-                return BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS;
-            case APP_REG_STATE_REG_FAILED:
-                return BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE;
-            case APP_REG_STATE_DEREG_SUCCESS:
-                return BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS;
-            case APP_REG_STATE_DEREG_FAILED:
-                return BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE;
-        }
-        Log.e(TAG, "Unexpected App Registration state: " + halRegStatus);
-        return BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE;
-    }
-
-    private int convertHalChannelState(int halChannelState) {
-        switch (halChannelState) {
-            case CONN_STATE_CONNECTED:
-                return BluetoothHealth.STATE_CHANNEL_CONNECTED;
-            case CONN_STATE_CONNECTING:
-                return BluetoothHealth.STATE_CHANNEL_CONNECTING;
-            case CONN_STATE_DISCONNECTING:
-                return BluetoothHealth.STATE_CHANNEL_DISCONNECTING;
-            case CONN_STATE_DISCONNECTED:
-                return BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
-            case CONN_STATE_DESTROYED:
-                // TODO(BT) add BluetoothHealth.STATE_CHANNEL_DESTROYED;
-                return BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
-            default:
-                Log.e(TAG, "Unexpected channel state: " + halChannelState);
-                return BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
-        }
-    }
-
-    private boolean connectChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config,
-            int channelType) {
-        if (mApps.get(config) == null) {
-            Log.e(TAG, "connectChannel fail to get a app id from config");
-            return false;
-        }
-
-        HealthChannel chan = new HealthChannel(device, config, channelType);
-
-        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_CHANNEL);
-        msg.obj = chan;
-        mHandler.sendMessage(msg);
-
-        return true;
-    }
-
-    private void callHealthChannelCallback(BluetoothHealthAppConfiguration config,
-            BluetoothDevice device, int state, int prevState, ParcelFileDescriptor fd, int id) {
-        broadcastHealthDeviceStateChange(device, state);
-
-        Log.d(TAG,
-                "Health Device Callback: " + device + " State Change: " + prevState + "->" + state);
-
-        ParcelFileDescriptor dupedFd = null;
-        if (fd != null) {
-            try {
-                dupedFd = fd.dup();
-            } catch (IOException e) {
-                dupedFd = null;
-                Log.e(TAG, "Exception while duping: " + e);
-            }
-        }
-
-        IBluetoothHealthCallback callback = (mApps.get(config)).mCallback;
-        if (callback == null) {
-            Log.e(TAG, "No callback found for config: " + config);
-            return;
-        }
-
-        try {
-            callback.onHealthChannelStateChange(config, device, prevState, state, dupedFd, id);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Remote Exception:" + e);
-        }
-    }
-
-    /**
-     * This function sends the intent for the updates on the connection status to the remote device.
-     * Note that multiple channels can be connected to the remote device by multiple applications.
-     * This sends an intent for the update to the device connection status and not the channel
-     * connection status. Only the following state transitions are possible:
-     *
-     * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTING}
-     * {@link BluetoothHealth#STATE_CONNECTING} to {@link BluetoothHealth#STATE_CONNECTED}
-     * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTING}
-     * {@link BluetoothHealth#STATE_DISCONNECTING} to {@link BluetoothHealth#STATE_DISCONNECTED}
-     * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTED}
-     * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTED}
-     * {@link BluetoothHealth#STATE_CONNECTING} to {{@link BluetoothHealth#STATE_DISCONNECTED}
-     *
-     * @param device
-     * @param prevChannelState
-     * @param newChannelState
-     * @hide
-     */
-    private void broadcastHealthDeviceStateChange(BluetoothDevice device, int newChannelState) {
-        if (mHealthDevices.get(device) == null) {
-            mHealthDevices.put(device, BluetoothHealth.STATE_DISCONNECTED);
-        }
-
-        int currDeviceState = mHealthDevices.get(device);
-        int newDeviceState = convertState(newChannelState);
-
-        if (currDeviceState == newDeviceState) {
-            return;
-        }
-
-        boolean sendIntent = false;
-        List<HealthChannel> chan;
-        switch (currDeviceState) {
-            case BluetoothHealth.STATE_DISCONNECTED:
-                // there was no connection or connect/disconnect attemp with the remote device
-                sendIntent = true;
-                break;
-            case BluetoothHealth.STATE_CONNECTING:
-                // there was no connection, there was a connecting attempt going on
-
-                // Channel got connected.
-                if (newDeviceState == BluetoothHealth.STATE_CONNECTED) {
-                    sendIntent = true;
-                } else {
-                    // Channel got disconnected
-                    chan = findChannelByStates(device, new int[]{
-                            BluetoothHealth.STATE_CHANNEL_CONNECTING,
-                            BluetoothHealth.STATE_CHANNEL_DISCONNECTING
-                    });
-                    if (chan.isEmpty()) {
-                        sendIntent = true;
-                    }
-                }
-                break;
-            case BluetoothHealth.STATE_CONNECTED:
-                // there was at least one connection
-
-                // Channel got disconnected or is in disconnecting state.
-                chan = findChannelByStates(device, new int[]{
-                        BluetoothHealth.STATE_CHANNEL_CONNECTING,
-                        BluetoothHealth.STATE_CHANNEL_CONNECTED
-                });
-                if (chan.isEmpty()) {
-                    sendIntent = true;
-                }
-                break;
-            case BluetoothHealth.STATE_DISCONNECTING:
-                // there was no connected channel with the remote device
-                // We were disconnecting all the channels with the remote device
-
-                // Channel got disconnected.
-                chan = findChannelByStates(device, new int[]{
-                        BluetoothHealth.STATE_CHANNEL_CONNECTING,
-                        BluetoothHealth.STATE_CHANNEL_DISCONNECTING
-                });
-                if (chan.isEmpty()) {
-                    updateAndSendIntent(device, newDeviceState, currDeviceState);
-                }
-                break;
-        }
-        if (sendIntent) {
-            updateAndSendIntent(device, newDeviceState, currDeviceState);
-        }
-    }
-
-    private void updateAndSendIntent(BluetoothDevice device, int newDeviceState,
-            int prevDeviceState) {
-        if (newDeviceState == BluetoothHealth.STATE_DISCONNECTED) {
-            mHealthDevices.remove(device);
-        } else {
-            mHealthDevices.put(device, newDeviceState);
-        }
-        if (newDeviceState != prevDeviceState
-                && newDeviceState == BluetoothHealth.STATE_CONNECTED) {
-            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HEALTH);
-        }
-    }
-
-    /**
-     * This function converts the channel connection state to device connection state.
-     *
-     * @param state
-     * @return
-     */
-    private int convertState(int state) {
-        switch (state) {
-            case BluetoothHealth.STATE_CHANNEL_CONNECTED:
-                return BluetoothHealth.STATE_CONNECTED;
-            case BluetoothHealth.STATE_CHANNEL_CONNECTING:
-                return BluetoothHealth.STATE_CONNECTING;
-            case BluetoothHealth.STATE_CHANNEL_DISCONNECTING:
-                return BluetoothHealth.STATE_DISCONNECTING;
-            case BluetoothHealth.STATE_CHANNEL_DISCONNECTED:
-                return BluetoothHealth.STATE_DISCONNECTED;
-        }
-        Log.e(TAG, "Mismatch in Channel and Health Device State: " + state);
-        return BluetoothHealth.STATE_DISCONNECTED;
-    }
-
-    private int convertRoleToHal(int role) {
-        if (role == BluetoothHealth.SOURCE_ROLE) {
-            return MDEP_ROLE_SOURCE;
-        }
-        if (role == BluetoothHealth.SINK_ROLE) {
-            return MDEP_ROLE_SINK;
-        }
-        Log.e(TAG, "unkonw role: " + role);
-        return MDEP_ROLE_SINK;
-    }
-
-    private int convertChannelTypeToHal(int channelType) {
-        if (channelType == BluetoothHealth.CHANNEL_TYPE_RELIABLE) {
-            return CHANNEL_TYPE_RELIABLE;
-        }
-        if (channelType == BluetoothHealth.CHANNEL_TYPE_STREAMING) {
-            return CHANNEL_TYPE_STREAMING;
-        }
-        if (channelType == BluetoothHealth.CHANNEL_TYPE_ANY) {
-            return CHANNEL_TYPE_ANY;
-        }
-        Log.e(TAG, "unkonw channel type: " + channelType);
-        return CHANNEL_TYPE_ANY;
-    }
-
-    private HealthChannel findChannelById(int id) {
-        for (HealthChannel chan : mHealthChannels) {
-            if (chan.mChannelId == id) {
-                return chan;
-            }
-        }
-        Log.e(TAG, "No channel found by id: " + id);
-        return null;
-    }
-
-    private List<HealthChannel> findChannelByStates(BluetoothDevice device, int[] states) {
-        List<HealthChannel> channels = new ArrayList<HealthChannel>();
-        for (HealthChannel chan : mHealthChannels) {
-            if (chan.mDevice.equals(device)) {
-                for (int state : states) {
-                    if (chan.mState == state) {
-                        channels.add(chan);
-                    }
-                }
-            }
-        }
-        return channels;
-    }
-
-    private int getConnectionState(BluetoothDevice device) {
-        if (mHealthDevices.get(device) == null) {
-            return BluetoothHealth.STATE_DISCONNECTED;
-        }
-        return mHealthDevices.get(device);
-    }
-
-    List<BluetoothDevice> lookupHealthDevicesMatchingStates(int[] states) {
-        List<BluetoothDevice> healthDevices = new ArrayList<BluetoothDevice>();
-
-        for (BluetoothDevice device : mHealthDevices.keySet()) {
-            int healthDeviceState = getConnectionState(device);
-            for (int state : states) {
-                if (state == healthDeviceState) {
-                    healthDevices.add(device);
-                    break;
-                }
-            }
-        }
-        return healthDevices;
-    }
-
-    @Override
-    public void dump(StringBuilder sb) {
-        super.dump(sb);
-        println(sb, "mHealthChannels:");
-        for (HealthChannel channel : mHealthChannels) {
-            println(sb, "  " + channel);
-        }
-        println(sb, "mApps:");
-        for (BluetoothHealthAppConfiguration conf : mApps.keySet()) {
-            println(sb, "  " + conf + " : " + mApps.get(conf));
-        }
-        println(sb, "mHealthDevices:");
-        for (BluetoothDevice device : mHealthDevices.keySet()) {
-            println(sb, "  " + device + " : " + mHealthDevices.get(device));
-        }
-    }
-
-    private static class AppInfo {
-        private IBluetoothHealthCallback mCallback;
-        private BluetoothHealthDeathRecipient mRcpObj;
-        private int mAppId;
-
-        private AppInfo(IBluetoothHealthCallback callback) {
-            mCallback = callback;
-            mRcpObj = null;
-            mAppId = -1;
-        }
-
-        private void cleanup() {
-            if (mCallback != null) {
-                if (mRcpObj != null) {
-                    IBinder binder = mCallback.asBinder();
-                    try {
-                        binder.unlinkToDeath(mRcpObj, 0);
-                    } catch (NoSuchElementException e) {
-                        Log.e(TAG, "No death recipient registered" + e);
-                    }
-                    mRcpObj.cleanup();
-                    mRcpObj = null;
-                }
-                mCallback = null;
-            } else if (mRcpObj != null) {
-                mRcpObj.cleanup();
-                mRcpObj = null;
-            }
-        }
-    }
-
-    private class HealthChannel {
-        private ParcelFileDescriptor mChannelFd;
-        private BluetoothDevice mDevice;
-        private BluetoothHealthAppConfiguration mConfig;
-        // BluetoothHealth channel state
-        private int mState;
-        private int mChannelType;
-        private int mChannelId;
-
-        private HealthChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config,
-                int channelType) {
-            mChannelFd = null;
-            mDevice = device;
-            mConfig = config;
-            mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
-            mChannelType = channelType;
-            mChannelId = -1;
-        }
-    }
-
-    // Channel state event from Hal
-    private class ChannelStateEvent {
-        int mAppId;
-        byte[] mAddr;
-        int mCfgIndex;
-        int mChannelId;
-        int mState;
-        FileDescriptor mFd;
-
-        private ChannelStateEvent(int appId, byte[] addr, int cfgIndex, int channelId, int state,
-                FileDescriptor fileDescriptor) {
-            mAppId = appId;
-            mAddr = addr;
-            mCfgIndex = cfgIndex;
-            mState = state;
-            mChannelId = channelId;
-            mFd = fileDescriptor;
-        }
-    }
-
-    // Constants matching Hal header file bt_hl.h
-    // bthl_app_reg_state_t
-    private static final int APP_REG_STATE_REG_SUCCESS = 0;
-    private static final int APP_REG_STATE_REG_FAILED = 1;
-    private static final int APP_REG_STATE_DEREG_SUCCESS = 2;
-    private static final int APP_REG_STATE_DEREG_FAILED = 3;
-
-    // bthl_channel_state_t
-    private static final int CONN_STATE_CONNECTING = 0;
-    private static final int CONN_STATE_CONNECTED = 1;
-    private static final int CONN_STATE_DISCONNECTING = 2;
-    private static final int CONN_STATE_DISCONNECTED = 3;
-    private static final int CONN_STATE_DESTROYED = 4;
-
-    // bthl_mdep_role_t
-    private static final int MDEP_ROLE_SOURCE = 0;
-    private static final int MDEP_ROLE_SINK = 1;
-
-    // bthl_channel_type_t
-    private static final int CHANNEL_TYPE_RELIABLE = 0;
-    private static final int CHANNEL_TYPE_STREAMING = 1;
-    private static final int CHANNEL_TYPE_ANY = 2;
-
-    private static native void classInitNative();
-
-    private native void initializeNative();
-
-    private native void cleanupNative();
-
-    private native int registerHealthAppNative(int dataType, int role, String name,
-            int channelType);
-
-    private native boolean unregisterHealthAppNative(int appId);
-
-    private native int connectChannelNative(byte[] btAddress, int appId);
-
-    private native boolean disconnectChannelNative(int channelId);
-
-}
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
index e735a88..b32d304 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
@@ -23,11 +23,11 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import com.android.bluetooth.Utils;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * HearingAid Native Interface to/from JNI.
@@ -69,7 +69,7 @@
      *
      * priorities to configure.
      */
-    @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void init() {
         initNative();
     }
@@ -77,7 +77,7 @@
     /**
      * Cleanup the native interface.
      */
-    @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void cleanup() {
         cleanupNative();
     }
@@ -88,7 +88,7 @@
      * @param device the remote device
      * @return true on success, otherwise false.
      */
-    @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public boolean connectHearingAid(BluetoothDevice device) {
         return connectHearingAidNative(getByteAddress(device));
     }
@@ -99,7 +99,7 @@
      * @param device the remote device
      * @return true on success, otherwise false.
      */
-    @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public boolean disconnectHearingAid(BluetoothDevice device) {
         return disconnectHearingAidNative(getByteAddress(device));
     }
@@ -110,27 +110,16 @@
      * @param device the remote device
      * @return true on success, otherwise false.
      */
-    @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public boolean addToWhiteList(BluetoothDevice device) {
         return addToWhiteListNative(getByteAddress(device));
     }
 
     /**
-     * Remove a hearing aid device from white list.
-     *
-     * @param device the remote device
-     * @return true on success, otherwise false.
-     */
-    @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-    public boolean removeFromWhiteList(BluetoothDevice device) {
-        return removeFromWhiteListNative(getByteAddress(device));
-    }
-
-    /**
      * Sets the HearingAid volume
      * @param volume
      */
-    @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void setVolume(int volume) {
         setVolumeNative(volume);
     }
@@ -191,6 +180,5 @@
     private native boolean connectHearingAidNative(byte[] address);
     private native boolean disconnectHearingAidNative(byte[] address);
     private native boolean addToWhiteListNative(byte[] address);
-    private native boolean removeFromWhiteListNative(byte[] address);
     private native void setVolumeNative(int volume);
 }
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidService.java b/src/com/android/bluetooth/hearingaid/HearingAidService.java
index 704a3d5..f29d9b9 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidService.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -28,15 +28,17 @@
 import android.media.AudioManager;
 import android.os.HandlerThread;
 import android.os.ParcelUuid;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
+import android.util.StatsLog;
 
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.ServiceFactory;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -50,16 +52,12 @@
  * @hide
  */
 public class HearingAidService extends ProfileService {
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
     private static final String TAG = "HearingAidService";
 
     // Upper limit of all HearingAid devices: Bonded or Connected
     private static final int MAX_HEARING_AID_STATE_MACHINES = 10;
     private static HearingAidService sHearingAidService;
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    static int sConnectTimeoutForEachSideMs = 8000;
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    static int sCheckWhitelistTimeoutMs = 16000;
 
     private AdapterService mAdapterService;
     private HandlerThread mStateMachinesThread;
@@ -74,11 +72,14 @@
             new HashMap<>();
     private final Map<BluetoothDevice, Long> mDeviceHiSyncIdMap = new ConcurrentHashMap<>();
     private final Map<BluetoothDevice, Integer> mDeviceCapabilitiesMap = new HashMap<>();
+    private final Map<Long, Boolean> mHiSyncIdConnectedMap = new HashMap<>();
     private long mActiveDeviceHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
 
     private BroadcastReceiver mBondStateChangedReceiver;
     private BroadcastReceiver mConnectionStateChangedReceiver;
 
+    private final ServiceFactory mFactory = new ServiceFactory();
+
     @Override
     protected IProfileServiceBinder initBinder() {
         return new BluetoothHearingAidBinder(this);
@@ -115,9 +116,10 @@
         mStateMachinesThread = new HandlerThread("HearingAidService.StateMachines");
         mStateMachinesThread.start();
 
-        // Clear HiSyncId map and capabilities map
+        // Clear HiSyncId map, capabilities map and HiSyncId Connected map
         mDeviceHiSyncIdMap.clear();
         mDeviceCapabilitiesMap.clear();
+        mHiSyncIdConnectedMap.clear();
 
         // Setup broadcast receivers
         IntentFilter filter = new IntentFilter();
@@ -170,9 +172,10 @@
             mStateMachines.clear();
         }
 
-        // Clear HiSyncId map and capabilities map
+        // Clear HiSyncId map, capabilities map and HiSyncId Connected map
         mDeviceHiSyncIdMap.clear();
         mDeviceCapabilitiesMap.clear();
+        mHiSyncIdConnectedMap.clear();
 
         if (mStateMachinesThread != null) {
             mStateMachinesThread.quitSafely();
@@ -247,6 +250,14 @@
             }
         }
 
+        synchronized (mStateMachines) {
+            HearingAidStateMachine smConnect = getOrCreateStateMachine(device);
+            if (smConnect == null) {
+                Log.e(TAG, "Cannot connect to " + device + " : no state machine");
+            }
+            smConnect.sendMessage(HearingAidStateMachine.CONNECT);
+        }
+
         for (BluetoothDevice storedDevice : mDeviceHiSyncIdMap.keySet()) {
             if (device.equals(storedDevice)) {
                 continue;
@@ -259,27 +270,14 @@
                         Log.e(TAG, "Ignored connect request for " + device + " : no state machine");
                         continue;
                     }
-                    sm.sendMessage(HearingAidStateMachine.CONNECT,
-                            sConnectTimeoutForEachSideMs);
-                    sm.sendMessageDelayed(HearingAidStateMachine.CHECK_WHITELIST_CONNECTION,
-                            sCheckWhitelistTimeoutMs);
+                    sm.sendMessage(HearingAidStateMachine.CONNECT);
                 }
-                break;
+                if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID
+                        && !device.equals(storedDevice)) {
+                    break;
+                }
             }
         }
-
-        synchronized (mStateMachines) {
-            HearingAidStateMachine smConnect = getOrCreateStateMachine(device);
-            if (smConnect == null) {
-                Log.e(TAG, "Cannot connect to " + device + " : no state machine");
-            } else {
-                smConnect.sendMessage(HearingAidStateMachine.CONNECT,
-                        sConnectTimeoutForEachSideMs * 2);
-                smConnect.sendMessageDelayed(HearingAidStateMachine.CHECK_WHITELIST_CONNECTION,
-                        sCheckWhitelistTimeoutMs);
-            }
-        }
-
         return true;
     }
 
@@ -329,13 +327,28 @@
     }
 
     /**
+     * Check any peer device is connected.
+     * The check considers any peer device is connected.
+     *
+     * @param device the peer device to connect to
+     * @return true if there are any peer device connected.
+     */
+    public boolean isConnectedPeerDevices(BluetoothDevice device) {
+        long hiSyncId = getHiSyncId(device);
+        if (getConnectedPeerDevices(hiSyncId).isEmpty()) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
      * Check whether can connect to a peer device.
      * The check considers a number of factors during the evaluation.
      *
      * @param device the peer device to connect to
      * @return true if connection is allowed, otherwise false
      */
-    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public boolean okToConnect(BluetoothDevice device) {
         // Check if this is an incoming connection in Quiet mode.
         if (mAdapterService.isQuietModeEnabled()) {
@@ -438,20 +451,18 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothHearingAidPriorityKey(device.getAddress()), priority);
         if (DBG) {
             Log.d(TAG, "Saved priority " + device + " = " + priority);
         }
+        mAdapterService.getDatabase()
+                .setProfilePriority(device, BluetoothProfile.HEARING_AID, priority);
         return true;
     }
 
     public int getPriority(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        int priority = Settings.Global.getInt(getContentResolver(),
-                Settings.Global.getBluetoothHearingAidPriorityKey(device.getAddress()),
-                BluetoothProfile.PRIORITY_UNDEFINED);
-        return priority;
+        return mAdapterService.getDatabase()
+                .getProfilePriority(device, BluetoothProfile.HEARING_AID);
     }
 
     void setVolume(int volume) {
@@ -494,6 +505,15 @@
             Long deviceHiSyncId = mDeviceHiSyncIdMap.getOrDefault(device,
                     BluetoothHearingAid.HI_SYNC_ID_INVALID);
             if (deviceHiSyncId != mActiveDeviceHiSyncId) {
+                // Give an early notification to A2DP that active device is being switched
+                // to Hearing Aids before the Audio Service.
+                final A2dpService a2dpService = mFactory.getA2dpService();
+                if (a2dpService != null) {
+                    if (DBG) {
+                        Log.d(TAG, "earlyNotifyHearingAidActive for " + device);
+                    }
+                    a2dpService.earlyNotifyHearingAidActive();
+                }
                 mActiveDeviceHiSyncId = deviceHiSyncId;
                 reportActiveDevice(device);
             }
@@ -508,7 +528,7 @@
      * device; the second element is the right active device. If either or both side
      * is not active, it will be null on that position
      */
-    List<BluetoothDevice> getActiveDevices() {
+    public List<BluetoothDevice> getActiveDevices() {
         if (DBG) {
             Log.d(TAG, "getActiveDevices");
         }
@@ -611,6 +631,9 @@
             Log.d(TAG, "reportActiveDevice(" + device + ")");
         }
 
+        StatsLog.write(StatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.HEARING_AID,
+                mAdapterService.obfuscateAddress(device));
+
         Intent intent = new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
@@ -731,10 +754,15 @@
                 MetricsLogger.logProfileConnectionEvent(
                         BluetoothMetricsProto.ProfileId.HEARING_AID);
             }
-            setActiveDevice(device);
+            if (!mHiSyncIdConnectedMap.getOrDefault(myHiSyncId, false)) {
+                setActiveDevice(device);
+                mHiSyncIdConnectedMap.put(myHiSyncId, true);
+            }
         }
         if (fromState == BluetoothProfile.STATE_CONNECTED && getConnectedDevices().isEmpty()) {
             setActiveDevice(null);
+            long myHiSyncId = getHiSyncId(device);
+            mHiSyncIdConnectedMap.put(myHiSyncId, false);
         }
         // Check if the device is disconnected - if unbond, remove the state machine
         if (toState == BluetoothProfile.STATE_DISCONNECTED) {
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
index 3e0d617..5b8d798 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
@@ -51,10 +51,10 @@
 import android.content.Intent;
 import android.os.Looper;
 import android.os.Message;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
@@ -69,21 +69,18 @@
 
     static final int CONNECT = 1;
     static final int DISCONNECT = 2;
-    static final int CHECK_WHITELIST_CONNECTION = 3;
     @VisibleForTesting
     static final int STACK_EVENT = 101;
     private static final int CONNECT_TIMEOUT = 201;
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    static int sConnectTimeoutMs = 16000;
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    static int sDisconnectTimeoutMs = 16000;
+    // NOTE: the value is not "final" - it is modified in the unit tests
+    @VisibleForTesting
+    static int sConnectTimeoutMs = 30000;        // 30s
 
     private Disconnected mDisconnected;
     private Connecting mConnecting;
     private Disconnecting mDisconnecting;
     private Connected mConnected;
-    private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
     private int mLastConnectionState = -1;
 
     private HearingAidService mService;
@@ -135,13 +132,13 @@
         public void enter() {
             Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + messageWhatToString(
                     getCurrentMessage().what));
-            mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
 
             removeDeferredMessages(DISCONNECT);
 
             if (mLastConnectionState != -1) {
                 // Don't broadcast during startup
-                broadcastConnectionState(mConnectionState, mLastConnectionState);
+                broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTED,
+                        mLastConnectionState);
             }
         }
 
@@ -172,13 +169,8 @@
                     }
                     break;
                 case DISCONNECT:
-                    Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice);
-                    break;
-                case CHECK_WHITELIST_CONNECTION:
-                    if (mService.getConnectedDevices().isEmpty()) {
-                        log("No device connected, remove this device from white list");
-                        mNativeInterface.removeFromWhiteList(mDevice);
-                    }
+                    Log.d(TAG, "Disconnected: DISCONNECT: call native disconnect for " + mDevice);
+                    mNativeInterface.disconnectHearingAid(mDevice);
                     break;
                 case STACK_EVENT:
                     HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
@@ -246,11 +238,8 @@
         public void enter() {
             Log.i(TAG, "Enter Connecting(" + mDevice + "): "
                     + messageWhatToString(getCurrentMessage().what));
-            int timeout = getCurrentMessage().arg1 != 0
-                    ? getCurrentMessage().arg1 : sConnectTimeoutMs;
-            sendMessageDelayed(CONNECT_TIMEOUT, timeout);
-            mConnectionState = BluetoothProfile.STATE_CONNECTING;
-            broadcastConnectionState(mConnectionState, mLastConnectionState);
+            sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
+            broadcastConnectionState(BluetoothProfile.STATE_CONNECTING, mLastConnectionState);
         }
 
         @Override
@@ -271,13 +260,18 @@
                     deferMessage(message);
                     break;
                 case CONNECT_TIMEOUT:
-                    Log.w(TAG, "Connecting connection timeout: " + mDevice + ". Try whitelist");
+                    Log.w(TAG, "Connecting connection timeout: " + mDevice);
                     mNativeInterface.disconnectHearingAid(mDevice);
-                    mNativeInterface.addToWhiteList(mDevice);
-                    transitionTo(mDisconnected);
-                    break;
-                case CHECK_WHITELIST_CONNECTION:
-                    deferMessage(message);
+                    if (mService.isConnectedPeerDevices(mDevice)) {
+                        Log.w(TAG, "One side connection timeout: " + mDevice + ". Try whitelist");
+                        mNativeInterface.addToWhiteList(mDevice);
+                    }
+                    HearingAidStackEvent disconnectEvent =
+                            new HearingAidStackEvent(
+                                    HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+                    disconnectEvent.device = mDevice;
+                    disconnectEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED;
+                    sendMessage(STACK_EVENT, disconnectEvent);
                     break;
                 case DISCONNECT:
                     log("Connecting: connection canceled to " + mDevice);
@@ -334,9 +328,8 @@
         public void enter() {
             Log.i(TAG, "Enter Disconnecting(" + mDevice + "): "
                     + messageWhatToString(getCurrentMessage().what));
-            sendMessageDelayed(CONNECT_TIMEOUT, sDisconnectTimeoutMs);
-            mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
-            broadcastConnectionState(mConnectionState, mLastConnectionState);
+            sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
+            broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTING, mLastConnectionState);
         }
 
         @Override
@@ -433,9 +426,8 @@
         public void enter() {
             Log.i(TAG, "Enter Connected(" + mDevice + "): "
                     + messageWhatToString(getCurrentMessage().what));
-            mConnectionState = BluetoothProfile.STATE_CONNECTED;
             removeDeferredMessages(CONNECT);
-            broadcastConnectionState(mConnectionState, mLastConnectionState);
+            broadcastConnectionState(BluetoothProfile.STATE_CONNECTED, mLastConnectionState);
         }
 
         @Override
@@ -489,7 +481,7 @@
         private void processConnectionEvent(int state) {
             switch (state) {
                 case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED:
-                    Log.i(TAG, "Disconnected from " + mDevice);
+                    Log.i(TAG, "Disconnected from " + mDevice + " but still in Whitelist");
                     transitionTo(mDisconnected);
                     break;
                 case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTING:
@@ -504,7 +496,20 @@
     }
 
     int getConnectionState() {
-        return mConnectionState;
+        String currentState = getCurrentState().getName();
+        switch (currentState) {
+            case "Disconnected":
+                return BluetoothProfile.STATE_DISCONNECTED;
+            case "Connecting":
+                return BluetoothProfile.STATE_CONNECTING;
+            case "Connected":
+                return BluetoothProfile.STATE_CONNECTED;
+            case "Disconnecting":
+                return BluetoothProfile.STATE_DISCONNECTING;
+            default:
+                Log.e(TAG, "Bad currentState: " + currentState);
+                return BluetoothProfile.STATE_DISCONNECTED;
+        }
     }
 
     BluetoothDevice getDevice() {
diff --git a/src/com/android/bluetooth/hfp/HeadsetCallState.java b/src/com/android/bluetooth/hfp/HeadsetCallState.java
index 3afd3c4..df45e75 100644
--- a/src/com/android/bluetooth/hfp/HeadsetCallState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetCallState.java
@@ -42,13 +42,19 @@
      * Phone number type
      */
     int mType;
+    /**
+     * Caller display name
+     */
+    String mName;
 
-    HeadsetCallState(int numActive, int numHeld, int callState, String number, int type) {
+    HeadsetCallState(int numActive, int numHeld, int callState, String number, int type,
+            String name) {
         mNumActive = numActive;
         mNumHeld = numHeld;
         mCallState = callState;
         mNumber = number;
         mType = type;
+        mName = name;
     }
 
     @Override
@@ -69,7 +75,13 @@
         } else {
             builder.append("***");
         }
-        builder.append(mNumber).append(", type=").append(mType).append("]");
+        builder.append(", type=").append(mType).append(", name=");
+        if (mName == null) {
+            builder.append("null");
+        } else {
+            builder.append("***");
+        }
+        builder.append("]");
     }
 
     @Override
@@ -83,11 +95,11 @@
         HeadsetCallState that = (HeadsetCallState) object;
         return mNumActive == that.mNumActive && mNumHeld == that.mNumHeld
                 && mCallState == that.mCallState && Objects.equals(mNumber, that.mNumber)
-                && mType == that.mType;
+                && mType == that.mType && Objects.equals(mName, that.mName);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mNumActive, mNumHeld, mCallState, mNumber, mType);
+        return Objects.hash(mNumActive, mNumHeld, mCallState, mNumber, mType, mName);
     }
 }
diff --git a/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java b/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
index b236d51..61c1163 100644
--- a/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
+++ b/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
@@ -18,10 +18,10 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import com.android.bluetooth.Utils;
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * Defines native calls that are used by state machine/service to either send or receive
@@ -127,9 +127,9 @@
         sendMessageToService(event);
     }
 
-    private void onNoiceReductionEnable(boolean enable, byte[] address) {
+    private void onNoiseReductionEnable(boolean enable, byte[] address) {
         HeadsetStackEvent event =
-                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_NOICE_REDUCTION, enable ? 1 : 0,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_NOISE_REDUCTION, enable ? 1 : 0,
                         getDevice(address));
         sendMessageToService(event);
     }
@@ -419,7 +419,7 @@
     @VisibleForTesting
     public boolean phoneStateChange(BluetoothDevice device, HeadsetCallState callState) {
         return phoneStateChangeNative(callState.mNumActive, callState.mNumHeld,
-                callState.mCallState, callState.mNumber, callState.mType,
+                callState.mCallState, callState.mNumber, callState.mType, callState.mName,
                 Utils.getByteAddress(device));
     }
 
@@ -493,7 +493,7 @@
     private native boolean copsResponseNative(String operatorName, byte[] address);
 
     private native boolean phoneStateChangeNative(int numActive, int numHeld, int callState,
-            String number, int type, byte[] address);
+            String number, int type, String name, byte[] address);
 
     private native boolean setScoAllowedNative(boolean value);
 
diff --git a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index bcf123c..941ec14 100644
--- a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -22,7 +22,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Looper;
-import android.support.annotation.VisibleForTesting;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -31,6 +30,7 @@
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.TelephonyIntents;
 
@@ -160,7 +160,7 @@
             return;
         }
         Log.i(TAG, "startListenForPhoneState(), subId=" + subId + ", enabled_events=" + events);
-        mPhoneStateListener = new HeadsetPhoneStateListener(subId,
+        mPhoneStateListener = new HeadsetPhoneStateListener(
                 mHeadsetService.getStateMachinesThreadLooper());
         mTelephonyManager.listen(mPhoneStateListener, events);
         if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
@@ -192,7 +192,7 @@
         return mNumActive;
     }
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void setNumActiveCall(int numActive) {
         mNumActive = numActive;
     }
@@ -201,7 +201,7 @@
         return mCallState;
     }
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void setCallState(int callState) {
         mCallState = callState;
     }
@@ -210,7 +210,7 @@
         return mNumHeld;
     }
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void setNumHeldCall(int numHeldCall) {
         mNumHeld = numHeldCall;
     }
@@ -228,7 +228,7 @@
      *
      * @param batteryLevel battery level value
      */
-    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void setCindBatteryCharge(int batteryLevel) {
         if (mCindBatteryCharge != batteryLevel) {
             mCindBatteryCharge = batteryLevel;
@@ -274,8 +274,8 @@
     }
 
     private class HeadsetPhoneStateListener extends PhoneStateListener {
-        HeadsetPhoneStateListener(Integer subId, Looper looper) {
-            super(subId, looper);
+        HeadsetPhoneStateListener(Looper looper) {
+            super(looper);
         }
 
         @Override
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index 6d16a6a..4b3da51 100644
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -39,9 +39,9 @@
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.telecom.PhoneAccount;
 import android.util.Log;
+import android.util.StatsLog;
 
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
@@ -61,7 +61,8 @@
  * Provides Bluetooth Headset and Handsfree profile, as a service in the Bluetooth application.
  *
  * Three modes for SCO audio:
- * Mode 1: Telecom call through {@link #phoneStateChanged(int, int, int, String, int, boolean)}
+ * Mode 1: Telecom call through {@link #phoneStateChanged(int, int, int, String, int, String,
+ *         boolean)}
  * Mode 2: Virtual call through {@link #startScoUsingVirtualVoiceCall()}
  * Mode 3: Voice recognition through {@link #startVoiceRecognition(BluetoothDevice)}
  *
@@ -219,7 +220,9 @@
         mStateMachinesThread.quitSafely();
         mStateMachinesThread = null;
         // Step 1: Clear
-        mAdapterService = null;
+        synchronized (mStateMachines) {
+            mAdapterService = null;
+        }
         return true;
     }
 
@@ -600,12 +603,12 @@
 
         @Override
         public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
-                int type) {
+                int type, String name) {
             HeadsetService service = getService();
             if (service == null) {
                 return;
             }
-            service.phoneStateChanged(numActive, numHeld, callState, number, type, false);
+            service.phoneStateChanged(numActive, numHeld, callState, number, type, name, false);
         }
 
         @Override
@@ -770,14 +773,14 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         ArrayList<BluetoothDevice> devices = new ArrayList<>();
-        if (states == null) {
-            return devices;
-        }
-        final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
-        if (bondedDevices == null) {
-            return devices;
-        }
         synchronized (mStateMachines) {
+            if (states == null || mAdapterService == null) {
+                return devices;
+            }
+            final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+            if (bondedDevices == null) {
+                return devices;
+            }
             for (BluetoothDevice device : bondedDevices) {
                 final ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
                 if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) {
@@ -808,18 +811,17 @@
 
     public boolean setPriority(BluetoothDevice device, int priority) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), priority);
         Log.i(TAG, "setPriority: device=" + device + ", priority=" + priority + ", "
                 + Utils.getUidPidString());
+        mAdapterService.getDatabase()
+                .setProfilePriority(device, BluetoothProfile.HEADSET, priority);
         return true;
     }
 
     public int getPriority(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return Settings.Global.getInt(getContentResolver(),
-                Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
-                BluetoothProfile.PRIORITY_UNDEFINED);
+        return mAdapterService.getDatabase()
+                .getProfilePriority(device, BluetoothProfile.HEADSET);
     }
 
     boolean startVoiceRecognition(BluetoothDevice device) {
@@ -1007,6 +1009,36 @@
     }
 
     /**
+     * Process a change in the silence mode for a {@link BluetoothDevice}.
+     *
+     * @param device the device to change silence mode
+     * @param silence true to enable silence mode, false to disable.
+     * @return true on success, false on error
+     */
+    @VisibleForTesting
+    public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
+        Log.d(TAG, "setSilenceMode(" + device + "): " + silence);
+
+        if (silence && Objects.equals(mActiveDevice, device)) {
+            setActiveDevice(null);
+        } else if (!silence && mActiveDevice == null) {
+            // Set the device as the active device if currently no active device.
+            setActiveDevice(device);
+        }
+        synchronized (mStateMachines) {
+            final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                Log.w(TAG, "setSilenceMode: device " + device
+                        + " was never connected/connecting");
+                return false;
+            }
+            stateMachine.setSilenceDevice(silence);
+        }
+
+        return true;
+    }
+
+    /**
      * Set the active device.
      *
      * @param device the active device
@@ -1221,9 +1253,9 @@
             }
             mVirtualCallStarted = true;
             // 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);
-            phoneStateChanged(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, true);
+            phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0, "", true);
+            phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0, "", true);
+            phoneStateChanged(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, "", true);
             return true;
         }
     }
@@ -1239,7 +1271,7 @@
             }
             mVirtualCallStarted = false;
             // 2. Send virtual phone state changed to close SCO
-            phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, true);
+            phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, "", true);
         }
         return true;
     }
@@ -1452,7 +1484,7 @@
     }
 
     private void phoneStateChanged(int numActive, int numHeld, int callState, String number,
-            int type, boolean isVirtualCall) {
+            int type, String name, boolean isVirtualCall) {
         enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
         synchronized (mStateMachines) {
             // Should stop all other audio mode in this case
@@ -1498,7 +1530,7 @@
         });
         doForEachConnectedStateMachine(
                 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
-                        new HeadsetCallState(numActive, numHeld, callState, number, type)));
+                        new HeadsetCallState(numActive, numHeld, callState, number, type, name)));
         mStateMachinesThread.getThreadHandler().post(() -> {
             if (callState == HeadsetHalConstants.CALL_STATE_IDLE
                     && mSystemInterface.isCallIdle() && !isAudioOn()) {
@@ -1668,6 +1700,8 @@
 
     private void broadcastActiveDevice(BluetoothDevice device) {
         logD("broadcastActiveDevice: " + device);
+        StatsLog.write(StatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.HEADSET,
+                mAdapterService.obfuscateAddress(device));
         Intent intent = new Intent(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
diff --git a/src/com/android/bluetooth/hfp/HeadsetStackEvent.java b/src/com/android/bluetooth/hfp/HeadsetStackEvent.java
index 200fb92..328ac08 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStackEvent.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStackEvent.java
@@ -33,7 +33,7 @@
     public static final int EVENT_TYPE_VOLUME_CHANGED = 6;
     public static final int EVENT_TYPE_DIAL_CALL = 7;
     public static final int EVENT_TYPE_SEND_DTMF = 8;
-    public static final int EVENT_TYPE_NOICE_REDUCTION = 9;
+    public static final int EVENT_TYPE_NOISE_REDUCTION = 9;
     public static final int EVENT_TYPE_AT_CHLD = 10;
     public static final int EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST = 11;
     public static final int EVENT_TYPE_AT_CIND = 12;
@@ -153,8 +153,8 @@
                 return "EVENT_TYPE_DIAL_CALL";
             case EVENT_TYPE_SEND_DTMF:
                 return "EVENT_TYPE_SEND_DTMF";
-            case EVENT_TYPE_NOICE_REDUCTION:
-                return "EVENT_TYPE_NOICE_REDUCTION";
+            case EVENT_TYPE_NOISE_REDUCTION:
+                return "EVENT_TYPE_NOISE_REDUCTION";
             case EVENT_TYPE_AT_CHLD:
                 return "EVENT_TYPE_AT_CHLD";
             case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index d25d9ed..c5485f9 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -16,23 +16,28 @@
 
 package com.android.bluetooth.hfp;
 
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAssignedNumbers;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProtoEnums;
+import android.bluetooth.hfp.BluetoothHfpProtoEnums;
 import android.content.Intent;
 import android.media.AudioManager;
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.support.annotation.VisibleForTesting;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.PhoneStateListener;
+import android.text.TextUtils;
 import android.util.Log;
+import android.util.StatsLog;
 
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
@@ -128,6 +133,7 @@
     // Runtime states
     private int mSpeakerVolume;
     private int mMicVolume;
+    private boolean mDeviceSilenced;
     private HeadsetAgIndicatorEnableState mAgIndicatorEnableState;
     // The timestamp when the device entered connecting/connected state
     private long mConnectingTimestampMs = Long.MIN_VALUE;
@@ -170,6 +176,7 @@
         mSystemInterface =
                 Objects.requireNonNull(systemInterface, "systemInterface cannot be null");
         mAdapterService = Objects.requireNonNull(adapterService, "AdapterService cannot be null");
+        mDeviceSilenced = false;
         // Create phonebook helper
         mPhonebook = new AtPhonebook(mHeadsetService, mNativeInterface);
         // Initialize state machine
@@ -299,6 +306,12 @@
         // Should not be called from enter() method
         void broadcastAudioState(BluetoothDevice device, int fromState, int toState) {
             stateLogD("broadcastAudioState: " + device + ": " + fromState + "->" + toState);
+            StatsLog.write(StatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED,
+                    mAdapterService.obfuscateAddress(device),
+                    getConnectionStateFromAudioState(toState),
+                    TextUtils.equals(mAudioParams.get(HEADSET_WBS), HEADSET_AUDIO_FEATURE_ON)
+                            ? BluetoothHfpProtoEnums.SCO_CODEC_MSBC
+                            : BluetoothHfpProtoEnums.SCO_CODEC_CVSD);
             mHeadsetService.onAudioStateChangedFromStateMachine(device, fromState, toState);
             Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
             intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState);
@@ -527,8 +540,9 @@
     }
 
     // Per HFP 1.7.1 spec page 23/144, Pending state needs to handle
-    //      AT+BRSF, AT+CIND, AT+CMER, AT+BIND, +CHLD
+    //      AT+BRSF, AT+CIND, AT+CMER, AT+BIND, AT+CHLD
     // commands during SLC establishment
+    // AT+CHLD=? will be handled by statck directly
     class Connecting extends HeadsetStateBase {
         @Override
         int getConnectionStateInt() {
@@ -585,9 +599,6 @@
                         case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
                             processConnectionEvent(message, event.valueInt);
                             break;
-                        case HeadsetStackEvent.EVENT_TYPE_AT_CHLD:
-                            processAtChld(event.valueInt, event.device);
-                            break;
                         case HeadsetStackEvent.EVENT_TYPE_AT_CIND:
                             processAtCind(event.device);
                             break;
@@ -830,6 +841,8 @@
                     break;
                 }
                 case CALL_STATE_CHANGED: {
+                    if (mDeviceSilenced) break;
+
                     HeadsetCallState callState = (HeadsetCallState) message.obj;
                     if (!mNativeInterface.phoneStateChange(mDevice, callState)) {
                         stateLogW("processCallState: failed to update call state " + callState);
@@ -921,7 +934,7 @@
                         case HeadsetStackEvent.EVENT_TYPE_SEND_DTMF:
                             mSystemInterface.sendDtmf(event.valueInt, event.device);
                             break;
-                        case HeadsetStackEvent.EVENT_TYPE_NOICE_REDUCTION:
+                        case HeadsetStackEvent.EVENT_TYPE_NOISE_REDUCTION:
                             processNoiseReductionEvent(event.valueInt == 1);
                             break;
                         case HeadsetStackEvent.EVENT_TYPE_WBS:
@@ -1011,9 +1024,6 @@
         @Override
         public void enter() {
             super.enter();
-            if (mConnectingTimestampMs == Long.MIN_VALUE) {
-                mConnectingTimestampMs = SystemClock.uptimeMillis();
-            }
             if (mPrevState == mConnecting) {
                 // Reset AG indicator subscriptions, HF can set this later using AT+BIA command
                 updateAgIndicatorEnableState(DEFAULT_AG_INDICATOR_ENABLE_STATE);
@@ -1427,6 +1437,27 @@
         return mConnectingTimestampMs;
     }
 
+    /**
+     * Set the silence mode status of this state machine
+     *
+     * @param silence true to enter silence mode, false on exit
+     * @return true on success, false on error
+     */
+    @VisibleForTesting
+    public boolean setSilenceDevice(boolean silence) {
+        if (silence == mDeviceSilenced) {
+            return false;
+        }
+        if (silence) {
+            mSystemInterface.getHeadsetPhoneState().listenForPhoneState(mDevice,
+                    PhoneStateListener.LISTEN_NONE);
+        } else {
+            updateAgIndicatorEnableState(mAgIndicatorEnableState);
+        }
+        mDeviceSilenced = silence;
+        return true;
+    }
+
     /*
      * Put the AT command, company ID, arguments, and device in an Intent and broadcast it.
      */
@@ -1803,6 +1834,18 @@
             Log.w(TAG, "processAtXapl() argument types not match");
             return;
         }
+        String[] deviceInfo = ((String) args[0]).split("-");
+        if (deviceInfo.length != 3) {
+            Log.w(TAG, "processAtXapl() deviceInfo length " + deviceInfo.length + " is wrong");
+            return;
+        }
+        String vendorId = deviceInfo[0];
+        String productId = deviceInfo[1];
+        String version = deviceInfo[2];
+        StatsLog.write(StatsLog.BLUETOOTH_DEVICE_INFO_REPORTED,
+                mAdapterService.obfuscateAddress(device), BluetoothProtoEnums.DEVICE_INFO_INTERNAL,
+                BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XAPL, vendorId, productId, version,
+                null);
         // feature = 2 indicates that we support battery level reporting only
         mNativeInterface.atResponseString(device, "+XAPL=iPhone," + String.valueOf(2));
     }
@@ -1880,19 +1923,15 @@
     private void processAtBind(String atString, BluetoothDevice device) {
         log("processAtBind: " + atString);
 
-        // Parse the AT String to find the Indicator Ids that are supported
-        int indId = 0;
-        int iter = 0;
-        int iter1 = 0;
+        for (String id : atString.split(",")) {
 
-        while (iter < atString.length()) {
-            iter1 = findChar(',', atString, iter);
-            String id = atString.substring(iter, iter1);
+            int indId;
 
             try {
-                indId = Integer.valueOf(id);
+                indId = Integer.parseInt(id);
             } catch (NumberFormatException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                continue;
             }
 
             switch (indId) {
@@ -1908,8 +1947,6 @@
                     log("Invalid HF Indicator Received");
                     break;
             }
-
-            iter = iter1 + 1; // move past comma
         }
     }
 
@@ -1947,7 +1984,8 @@
 
     private void updateAgIndicatorEnableState(
             HeadsetAgIndicatorEnableState agIndicatorEnableState) {
-        if (Objects.equals(mAgIndicatorEnableState, agIndicatorEnableState)) {
+        if (!mDeviceSilenced
+                && Objects.equals(mAgIndicatorEnableState, agIndicatorEnableState)) {
             Log.i(TAG, "updateAgIndicatorEnableState, no change in indicator state "
                     + mAgIndicatorEnableState);
             return;
@@ -2024,6 +2062,18 @@
         }
     }
 
+    private static int getConnectionStateFromAudioState(int audioState) {
+        switch (audioState) {
+            case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+                return BluetoothAdapter.STATE_CONNECTED;
+            case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+                return BluetoothAdapter.STATE_CONNECTING;
+            case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+                return BluetoothAdapter.STATE_DISCONNECTED;
+        }
+        return BluetoothAdapter.STATE_DISCONNECTED;
+    }
+
     private static String getMessageName(int what) {
         switch (what) {
             case CONNECT:
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java b/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java
index 88ae579..d7b5067 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java
@@ -170,6 +170,7 @@
     // used for sending vendor specific AT cmds to AG.
 
     static final int HANDSFREECLIENT_AT_CMD_NREC = 15;
+    static final int HANDSFREECLIENT_AT_CMD_VENDOR_SPECIFIC_CMD = 16;
 
     // Flag to check for local NREC support
     static final boolean HANDSFREECLIENT_NREC_SUPPORTED = true;
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
index 46c017c..474d332 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
@@ -29,10 +29,10 @@
 import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.Message;
-import android.provider.Settings;
 import android.util.Log;
 
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
 
@@ -64,10 +64,6 @@
 
     public static final String HFP_CLIENT_STOP_TAG = "hfp_client_stop_tag";
 
-    static {
-        NativeInterface.classInitNative();
-    }
-
     @Override
     public IProfileServiceBinder initBinder() {
         return new BluetoothHeadsetClientBinder(this);
@@ -78,8 +74,15 @@
         if (DBG) {
             Log.d(TAG, "start()");
         }
+        if (sHeadsetClientService != null) {
+            Log.w(TAG, "start(): start called without stop");
+            return false;
+        }
+
         // Setup the JNI service
-        NativeInterface.initializeNative();
+        mNativeInterface = NativeInterface.getInstance();
+        mNativeInterface.initialize();
+
         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
         if (mAudioManager == null) {
             Log.e(TAG, "AudioManager service doesn't exist?");
@@ -94,8 +97,6 @@
         IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
         registerReceiver(mBroadcastReceiver, filter);
 
-        mNativeInterface = new NativeInterface();
-
         // Start the HfpClientConnectionService to create connection with telecom when HFP
         // connection is available.
         Intent startIntent = new Intent(this, HfpClientConnectionService.class);
@@ -115,6 +116,11 @@
             Log.w(TAG, "stop() called without start()");
             return false;
         }
+
+        // Stop the HfpClientConnectionService.
+        Intent stopIntent = new Intent(this, HfpClientConnectionService.class);
+        sHeadsetClientService.stopService(stopIntent);
+
         setHeadsetClientService(null);
 
         unregisterReceiver(mBroadcastReceiver);
@@ -127,17 +133,12 @@
             it.remove();
         }
 
-        // Stop the HfpClientConnectionService.
-        Intent stopIntent = new Intent(this, HfpClientConnectionService.class);
-        stopIntent.putExtra(HFP_CLIENT_STOP_TAG, true);
-        startService(stopIntent);
-        mNativeInterface = null;
-
         // Stop the handler thread
         mSmThread.quit();
         mSmThread = null;
 
-        NativeInterface.cleanupNative();
+        mNativeInterface.cleanup();
+        mNativeInterface = null;
 
         return true;
     }
@@ -430,6 +431,15 @@
         }
 
         @Override
+        public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) {
+            HeadsetClientService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.sendVendorAtCommand(device, vendorId, atCommand);
+        }
+
+        @Override
         public Bundle getCurrentAgFeatures(BluetoothDevice device) {
             HeadsetClientService service = getService();
             if (service == null) {
@@ -537,20 +547,18 @@
 
     public boolean setPriority(BluetoothDevice device, int priority) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), priority);
         if (DBG) {
             Log.d(TAG, "Saved priority " + device + " = " + priority);
         }
+        AdapterService.getAdapterService().getDatabase()
+                .setProfilePriority(device, BluetoothProfile.HEADSET_CLIENT, priority);
         return true;
     }
 
     public int getPriority(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        int priority = Settings.Global.getInt(getContentResolver(),
-                Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
-                BluetoothProfile.PRIORITY_UNDEFINED);
-        return priority;
+        return AdapterService.getAdapterService().getDatabase()
+                .getProfilePriority(device, BluetoothProfile.HEADSET_CLIENT);
     }
 
     boolean startVoiceRecognition(BluetoothDevice device) {
@@ -821,6 +829,26 @@
         return true;
     }
 
+    /** Send vendor AT command. */
+    public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        HeadsetClientStateMachine sm = getStateMachine(device);
+        if (sm == null) {
+            Log.e(TAG, "Cannot allocate SM for device " + device);
+            return false;
+        }
+
+        int connectionState = sm.getConnectionState(device);
+        if (connectionState != BluetoothProfile.STATE_CONNECTED) {
+            return false;
+        }
+
+        Message msg = sm.obtainMessage(HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND,
+                                       vendorId, 0, atCommand);
+        sm.sendMessage(msg);
+        return true;
+    }
+
     public Bundle getCurrentAgEvents(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
@@ -886,7 +914,7 @@
 
         // Allocate a new SM
         Log.d(TAG, "Creating a new state machine");
-        sm = mSmFactory.make(this, mSmThread);
+        sm = mSmFactory.make(this, mSmThread, mNativeInterface);
         mStateMachineMap.put(device, sm);
         return sm;
     }
@@ -914,8 +942,6 @@
         super.dump(sb);
         for (HeadsetClientStateMachine sm : mStateMachineMap.values()) {
             if (sm != null) {
-                println(sb, "State machine:");
-                println(sb, "=============");
                 sm.dump(sb);
             }
         }
@@ -930,7 +956,7 @@
         mSmFactory = factory;
     }
 
-    AudioManager getAudioManager() {
+    protected AudioManager getAudioManager() {
         return mAudioManager;
     }
 }
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
index 844c470..3093aaf 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@@ -39,6 +39,7 @@
 import android.bluetooth.BluetoothHeadsetClientCall;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
+import android.bluetooth.hfp.BluetoothHfpProtoEnums;
 import android.content.Intent;
 import android.media.AudioAttributes;
 import android.media.AudioFocusRequest;
@@ -48,9 +49,9 @@
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.SystemClock;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 import android.util.Pair;
+import android.util.StatsLog;
 
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.R;
@@ -58,6 +59,7 @@
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -73,7 +75,7 @@
 
 public class HeadsetClientStateMachine extends StateMachine {
     private static final String TAG = "HeadsetClientStateMachine";
-    private static final boolean DBG = false;
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
     static final int NO_ACTION = 0;
     static final int IN_BAND_RING_ENABLED = 1;
@@ -97,6 +99,7 @@
     public static final int SEND_DTMF = 17;
     public static final int EXPLICIT_CALL_TRANSFER = 18;
     public static final int DISABLE_NREC = 20;
+    public static final int SEND_VENDOR_AT_COMMAND = 21;
 
     // internal actions
     private static final int QUERY_CURRENT_CALLS = 50;
@@ -172,7 +175,9 @@
     // This is returned when requesting focus from AudioManager
     private AudioFocusRequest mAudioFocusRequest;
 
-    private AudioManager mAudioManager;
+    private final AudioManager mAudioManager;
+    private final NativeInterface mNativeInterface;
+    private final VendorCommandResponseProcessor mVendorProcessor;
 
     // Accessor for the states, useful for reusing the state machines
     public IState getDisconnectedState() {
@@ -185,7 +190,9 @@
     }
 
     public void dump(StringBuilder sb) {
-        ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
+        if (mCurrentDevice == null) return;
+        ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice.getAddress() + "("
+                + mCurrentDevice.getName() + ") " + this.toString());
         ProfileService.println(sb, "mAudioState: " + mAudioState);
         ProfileService.println(sb, "mAudioWbs: " + mAudioWbs);
         ProfileService.println(sb, "mIndicatorNetworkState: " + mIndicatorNetworkState);
@@ -208,9 +215,6 @@
                 ProfileService.println(sb, "  " + call);
             }
         }
-
-        ProfileService.println(sb, "State machine stats:");
-        ProfileService.println(sb, this.toString());
     }
 
     private void clearPendingAction() {
@@ -230,9 +234,7 @@
     }
 
     private BluetoothHeadsetClientCall getCall(int... states) {
-        if (DBG) {
-            Log.d(TAG, "getFromCallsWithStates states:" + Arrays.toString(states));
-        }
+        logD("getFromCallsWithStates states:" + Arrays.toString(states));
         for (BluetoothHeadsetClientCall c : mCalls.values()) {
             for (int s : states) {
                 if (c.getState() == s) {
@@ -255,9 +257,7 @@
     }
 
     private void sendCallChangedIntent(BluetoothHeadsetClientCall c) {
-        if (DBG) {
-            Log.d(TAG, "sendCallChangedIntent " + c);
-        }
+        logD("sendCallChangedIntent " + c);
         Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CALL_CHANGED);
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         intent.putExtra(BluetoothHeadsetClient.EXTRA_CALL, c);
@@ -265,19 +265,15 @@
     }
 
     private boolean queryCallsStart() {
-        if (DBG) {
-            Log.d(TAG, "queryCallsStart");
-        }
+        logD("queryCallsStart");
         clearPendingAction();
-        NativeInterface.queryCurrentCallsNative(getByteAddress(mCurrentDevice));
+        mNativeInterface.queryCurrentCalls(getByteAddress(mCurrentDevice));
         addQueuedAction(QUERY_CURRENT_CALLS, 0);
         return true;
     }
 
     private void queryCallsDone() {
-        if (DBG) {
-            Log.d(TAG, "queryCallsDone");
-        }
+        logD("queryCallsDone");
         // mCalls has two types of calls:
         // (a) Calls that are received from AG of a previous iteration of queryCallsStart()
         // (b) Calls that are outgoing initiated from HF
@@ -324,11 +320,9 @@
         callRetainedIds.addAll(currCallIdSet);
         callRetainedIds.retainAll(newCallIdSet);
 
-        if (DBG) {
-            Log.d(TAG, "currCallIdSet " + mCalls.keySet() + " newCallIdSet " + newCallIdSet
-                    + " callAddedIds " + callAddedIds + " callRemovedIds " + callRemovedIds
-                    + " callRetainedIds " + callRetainedIds);
-        }
+        logD("currCallIdSet " + mCalls.keySet() + " newCallIdSet " + newCallIdSet
+                + " callAddedIds " + callAddedIds + " callRemovedIds " + callRemovedIds
+                + " callRetainedIds " + callRetainedIds);
 
         // First thing is to try to associate the outgoing HF with a valid call.
         Integer hfOriginatedAssoc = -1;
@@ -336,9 +330,7 @@
             BluetoothHeadsetClientCall c = mCalls.get(HF_ORIGINATED_CALL_ID);
             long cCreationElapsed = c.getCreationElapsedMilli();
             if (callAddedIds.size() > 0) {
-                if (DBG) {
-                    Log.d(TAG, "Associating the first call with HF originated call");
-                }
+                logD("Associating the first call with HF originated call");
                 hfOriginatedAssoc = (Integer) callAddedIds.toArray()[0];
                 mCalls.put(hfOriginatedAssoc, mCalls.get(HF_ORIGINATED_CALL_ID));
                 mCalls.remove(HF_ORIGINATED_CALL_ID);
@@ -366,11 +358,9 @@
             }
         }
 
-        if (DBG) {
-            Log.d(TAG, "ADJUST: currCallIdSet " + mCalls.keySet() + " newCallIdSet " + newCallIdSet
-                    + " callAddedIds " + callAddedIds + " callRemovedIds " + callRemovedIds
-                    + " callRetainedIds " + callRetainedIds);
-        }
+        logD("ADJUST: currCallIdSet " + mCalls.keySet() + " newCallIdSet " + newCallIdSet
+                + " callAddedIds " + callAddedIds + " callRemovedIds " + callRemovedIds
+                + " callRetainedIds " + callRetainedIds);
 
         // Terminate & remove the calls that are done.
         for (Integer idx : callRemovedIds) {
@@ -405,7 +395,7 @@
                 sendMessageDelayed(QUERY_CURRENT_CALLS, QUERY_CURRENT_CALLS_WAIT_MILLIS);
             } else {
                 if (getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING) != null) {
-                    Log.d(TAG, "Still have incoming call; polling");
+                    logD("Still have incoming call; polling");
                     sendMessageDelayed(QUERY_CURRENT_CALLS, QUERY_CURRENT_CALLS_WAIT_MILLIS);
                 } else {
                     removeMessages(QUERY_CURRENT_CALLS);
@@ -418,9 +408,7 @@
 
     private void queryCallsUpdate(int id, int state, String number, boolean multiParty,
             boolean outgoing) {
-        if (DBG) {
-            Log.d(TAG, "queryCallsUpdate: " + id);
-        }
+        logD("queryCallsUpdate: " + id);
         mCallsUpdate.put(id,
                 new BluetoothHeadsetClientCall(mCurrentDevice, id, state, number, multiParty,
                         outgoing, mInBandRing));
@@ -429,9 +417,7 @@
     private void acceptCall(int flag) {
         int action = -1;
 
-        if (DBG) {
-            Log.d(TAG, "acceptCall: (" + flag + ")");
-        }
+        logD("acceptCall: (" + flag + ")");
 
         BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING,
                 BluetoothHeadsetClientCall.CALL_STATE_WAITING);
@@ -444,9 +430,7 @@
             }
         }
 
-        if (DBG) {
-            Log.d(TAG, "Call to accept: " + c);
-        }
+        logD("Call to accept: " + c);
         switch (c.getState()) {
             case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
                 if (flag != BluetoothHeadsetClient.CALL_ACCEPT_NONE) {
@@ -468,14 +452,10 @@
                 // existing call or hold the existing call. We hold the other call by default.
                 if (flag == BluetoothHeadsetClient.CALL_ACCEPT_HOLD
                         || flag == BluetoothHeadsetClient.CALL_ACCEPT_NONE) {
-                    if (DBG) {
-                        Log.d(TAG, "Accepting call with accept and hold");
-                    }
+                    logD("Accepting call with accept and hold");
                     action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
                 } else if (flag == BluetoothHeadsetClient.CALL_ACCEPT_TERMINATE) {
-                    if (DBG) {
-                        Log.d(TAG, "Accepting call with accept and reject");
-                    }
+                    logD("Accepting call with accept and reject");
                     action = HeadsetClientHalConstants.CALL_ACTION_CHLD_1;
                 } else {
                     Log.e(TAG, "Aceept call with invalid flag: " + flag);
@@ -510,7 +490,7 @@
             routeHfpAudio(true);
         }
 
-        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) {
+        if (mNativeInterface.handleCallAction(getByteAddress(mCurrentDevice), action, 0)) {
             addQueuedAction(ACCEPT_CALL, action);
         } else {
             Log.e(TAG, "ERROR: Couldn't accept a call, action:" + action);
@@ -520,18 +500,14 @@
     private void rejectCall() {
         int action;
 
-        if (DBG) {
-            Log.d(TAG, "rejectCall");
-        }
+        logD("rejectCall");
 
         BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING,
                 BluetoothHeadsetClientCall.CALL_STATE_WAITING,
                 BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD,
                 BluetoothHeadsetClientCall.CALL_STATE_HELD);
         if (c == null) {
-            if (DBG) {
-                Log.d(TAG, "No call to reject, returning.");
-            }
+            logD("No call to reject, returning.");
             return;
         }
 
@@ -553,10 +529,8 @@
                 return;
         }
 
-        if (DBG) {
-            Log.d(TAG, "Reject call action " + action);
-        }
-        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) {
+        if (mNativeInterface.handleCallAction(getByteAddress(mCurrentDevice), action, 0)) {
+            logD("Reject call action " + action);
             addQueuedAction(REJECT_CALL, action);
         } else {
             Log.e(TAG, "ERROR: Couldn't reject a call, action:" + action);
@@ -566,9 +540,7 @@
     private void holdCall() {
         int action;
 
-        if (DBG) {
-            Log.d(TAG, "holdCall");
-        }
+        logD("holdCall");
 
         BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING);
         if (c != null) {
@@ -582,7 +554,7 @@
             action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
         }
 
-        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) {
+        if (mNativeInterface.handleCallAction(getByteAddress(mCurrentDevice), action, 0)) {
             addQueuedAction(HOLD_CALL, action);
         } else {
             Log.e(TAG, "ERROR: Couldn't hold a call, action:" + action);
@@ -590,9 +562,7 @@
     }
 
     private void terminateCall() {
-        if (DBG) {
-            Log.d(TAG, "terminateCall");
-        }
+        logD("terminateCall");
 
         int action = HeadsetClientHalConstants.CALL_ACTION_CHUP;
 
@@ -605,7 +575,7 @@
             action = HeadsetClientHalConstants.CALL_ACTION_CHLD_0;
         }
         if (c != null) {
-            if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) {
+            if (mNativeInterface.handleCallAction(getByteAddress(mCurrentDevice), action, 0)) {
                 addQueuedAction(TERMINATE_CALL, action);
             } else {
                 Log.e(TAG, "ERROR: Couldn't terminate outgoing call");
@@ -614,9 +584,7 @@
     }
 
     private void enterPrivateMode(int idx) {
-        if (DBG) {
-            Log.d(TAG, "enterPrivateMode: " + idx);
-        }
+        logD("enterPrivateMode: " + idx);
 
         BluetoothHeadsetClientCall c = mCalls.get(idx);
 
@@ -625,7 +593,7 @@
             return;
         }
 
-        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice),
+        if (mNativeInterface.handleCallAction(getByteAddress(mCurrentDevice),
                 HeadsetClientHalConstants.CALL_ACTION_CHLD_2X, idx)) {
             addQueuedAction(ENTER_PRIVATE_MODE, c);
         } else {
@@ -634,16 +602,14 @@
     }
 
     private void explicitCallTransfer() {
-        if (DBG) {
-            Log.d(TAG, "explicitCallTransfer");
-        }
+        logD("explicitCallTransfer");
 
         // can't transfer call if there is not enough call parties
         if (mCalls.size() < 2) {
             return;
         }
 
-        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice),
+        if (mNativeInterface.handleCallAction(getByteAddress(mCurrentDevice),
                 HeadsetClientHalConstants.CALL_ACTION_CHLD_4, -1)) {
             addQueuedAction(EXPLICIT_CALL_TRANSFER);
         } else {
@@ -657,6 +623,10 @@
                 == HeadsetClientHalConstants.PEER_FEAT_3WAY) {
             b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
         }
+        if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_VREC)
+                == HeadsetClientHalConstants.PEER_FEAT_VREC) {
+            b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION, true);
+        }
         if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)
                 == HeadsetClientHalConstants.PEER_FEAT_REJECT) {
             b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
@@ -692,11 +662,15 @@
         return b;
     }
 
-    HeadsetClientStateMachine(HeadsetClientService context, Looper looper) {
+    HeadsetClientStateMachine(HeadsetClientService context, Looper looper,
+                              NativeInterface nativeInterface) {
         super(TAG, looper);
         mService = context;
+        mNativeInterface = nativeInterface;
         mAudioManager = mService.getAudioManager();
 
+        mVendorProcessor = new VendorCommandResponseProcessor(mService, mNativeInterface);
+
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
         mAudioWbs = false;
@@ -732,11 +706,11 @@
         setInitialState(mDisconnected);
     }
 
-    static HeadsetClientStateMachine make(HeadsetClientService context, Looper l) {
-        if (DBG) {
-            Log.d(TAG, "make");
-        }
-        HeadsetClientStateMachine hfcsm = new HeadsetClientStateMachine(context, l);
+    static HeadsetClientStateMachine make(HeadsetClientService context, Looper looper,
+                                          NativeInterface nativeInterface) {
+        logD("make");
+        HeadsetClientStateMachine hfcsm = new HeadsetClientStateMachine(context, looper,
+                                                                        nativeInterface);
         hfcsm.start();
         return hfcsm;
     }
@@ -746,9 +720,7 @@
             Log.e(TAG, "AudioManager is null!");
             return;
         }
-        if (DBG) {
-            Log.d(TAG, "hfp_enable=" + enable);
-        }
+        logD("hfp_enable=" + enable);
         if (enable && !sAudioIsRouted) {
             mAudioManager.setParameters("hfp_enable=true");
         } else if (!enable) {
@@ -767,16 +739,17 @@
                         .setAudioAttributes(streamAttributes)
                         .build();
         int focusRequestStatus = mAudioManager.requestAudioFocus(focusRequest);
-        if (DBG) {
-            String s = (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
-                    ? "AudioFocus granted" : "AudioFocus NOT granted";
-            Log.d(TAG, "AudioManager requestAudioFocus returned: " + s);
-        }
+        String s = (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
+                ? "AudioFocus granted" : "AudioFocus NOT granted";
+        logD("AudioManager requestAudioFocus returned: " + s);
         return focusRequest;
     }
 
     public void doQuit() {
-        Log.d(TAG, "doQuit");
+        logD("doQuit");
+        if (mCurrentDevice != null) {
+            mNativeInterface.disconnect(getByteAddress(mCurrentDevice));
+        }
         routeHfpAudio(false);
         returnAudioFocusIfNecessary();
         quitNow();
@@ -793,7 +766,7 @@
         int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME;
         int amOffset = (amRange * (hfVol - MIN_HFP_SCO_VOICE_CALL_VOLUME)) / hfRange;
         int amVol = sMinAmVcVol + amOffset;
-        Log.d(TAG, "HF -> AM " + hfVol + " " + amVol);
+        logD("HF -> AM " + hfVol + " " + amVol);
         return amVol;
     }
 
@@ -802,14 +775,14 @@
         int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME;
         int hfOffset = (hfRange * (amVol - sMinAmVcVol)) / amRange;
         int hfVol = MIN_HFP_SCO_VOICE_CALL_VOLUME + hfOffset;
-        Log.d(TAG, "AM -> HF " + amVol + " " + hfVol);
+        logD("AM -> HF " + amVol + " " + hfVol);
         return hfVol;
     }
 
     class Disconnected extends State {
         @Override
         public void enter() {
-            Log.d(TAG, "Enter Disconnected: " + getCurrentMessage().what);
+            logD("Enter Disconnected: " + getCurrentMessage().what);
 
             // cleanup
             mIndicatorNetworkState = HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE;
@@ -851,7 +824,7 @@
 
         @Override
         public synchronized boolean processMessage(Message message) {
-            Log.d(TAG, "Disconnected process message: " + message.what);
+            logD("Disconnected process message: " + message.what);
 
             if (mCurrentDevice != null) {
                 Log.e(TAG, "ERROR: current device not null in Disconnected");
@@ -861,7 +834,7 @@
             switch (message.what) {
                 case CONNECT:
                     BluetoothDevice device = (BluetoothDevice) message.obj;
-                    if (!NativeInterface.connectNative(getByteAddress(device))) {
+                    if (!mNativeInterface.connect(getByteAddress(device))) {
                         // No state transition is involved, fire broadcast immediately
                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
                                 BluetoothProfile.STATE_DISCONNECTED);
@@ -875,15 +848,11 @@
                     break;
                 case StackEvent.STACK_EVENT:
                     StackEvent event = (StackEvent) message.obj;
-                    if (DBG) {
-                        Log.d(TAG, "Stack event type: " + event.type);
-                    }
+                    logD("Stack event type: " + event.type);
                     switch (event.type) {
                         case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            if (DBG) {
-                                Log.d(TAG, "Disconnected: Connection " + event.device
-                                        + " state changed:" + event.valueInt);
-                            }
+                            logD("Disconnected: Connection " + event.device
+                                    + " state changed:" + event.valueInt);
                             processConnectionEvent(event.valueInt, event.device);
                             break;
                         default:
@@ -911,7 +880,7 @@
                                 + " bondState=" + device.getBondState());
                         // reject the connection and stay in Disconnected state
                         // itself
-                        NativeInterface.disconnectNative(getByteAddress(device));
+                        mNativeInterface.disconnect(getByteAddress(device));
                         // the other profile connection should be initiated
                         AdapterService adapterService = AdapterService.getAdapterService();
                         // No state transition is involved, fire broadcast immediately
@@ -930,9 +899,7 @@
 
         @Override
         public void exit() {
-            if (DBG) {
-                Log.d(TAG, "Exit Disconnected: " + getCurrentMessage().what);
-            }
+            logD("Exit Disconnected: " + getCurrentMessage().what);
             mPrevState = this;
         }
     }
@@ -940,9 +907,7 @@
     class Connecting extends State {
         @Override
         public void enter() {
-            if (DBG) {
-                Log.d(TAG, "Enter Connecting: " + getCurrentMessage().what);
-            }
+            logD("Enter Connecting: " + getCurrentMessage().what);
             // This message is either consumed in processMessage or
             // removed in exit. It is safe to send a CONNECTING_TIMEOUT here since
             // the only transition is when connection attempt is initiated.
@@ -959,9 +924,7 @@
 
         @Override
         public synchronized boolean processMessage(Message message) {
-            if (DBG) {
-                Log.d(TAG, "Connecting process message: " + message.what);
-            }
+            logD("Connecting process message: " + message.what);
 
             switch (message.what) {
                 case CONNECT:
@@ -971,16 +934,11 @@
                     break;
                 case StackEvent.STACK_EVENT:
                     StackEvent event = (StackEvent) message.obj;
-                    if (DBG) {
-                        Log.d(TAG, "Connecting: event type: " + event.type);
-                    }
+                    logD("Connecting: event type: " + event.type);
                     switch (event.type) {
                         case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            if (DBG) {
-                                Log.d(TAG,
-                                        "Connecting: Connection " + event.device + " state changed:"
-                                                + event.valueInt);
-                            }
+                            logD("Connecting: Connection " + event.device + " state changed:"
+                                    + event.valueInt);
                             processConnectionEvent(event.valueInt, event.valueInt2, event.valueInt3,
                                     event.device);
                             break;
@@ -1029,14 +987,14 @@
                     break;
 
                 case HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED:
-                    Log.d(TAG, "HFPClient Connected from Connecting state");
+                    logD("HFPClient Connected from Connecting state");
 
                     mPeerFeatures = peerFeat;
                     mChldFeatures = chldFeat;
 
                     // We do not support devices which do not support enhanced call status (ECS).
                     if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECS) == 0) {
-                        NativeInterface.disconnectNative(getByteAddress(device));
+                        mNativeInterface.disconnect(getByteAddress(device));
                         return;
                     }
 
@@ -1044,7 +1002,7 @@
                     if (HeadsetClientHalConstants.HANDSFREECLIENT_NREC_SUPPORTED && (
                             (mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECNR)
                                     == HeadsetClientHalConstants.PEER_FEAT_ECNR)) {
-                        if (NativeInterface.sendATCmdNative(getByteAddress(mCurrentDevice),
+                        if (mNativeInterface.sendATCmd(getByteAddress(mCurrentDevice),
                                 HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_NREC, 1, 0,
                                 null)) {
                             addQueuedAction(DISABLE_NREC);
@@ -1080,9 +1038,7 @@
                     break;
                 case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING:
                     /* outgoing connecting started */
-                    if (DBG) {
-                        Log.d(TAG, "outgoing connection started, ignore");
-                    }
+                    logD("outgoing connection started, ignore");
                     break;
                 case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTING:
                 default:
@@ -1093,9 +1049,7 @@
 
         @Override
         public void exit() {
-            if (DBG) {
-                Log.d(TAG, "Exit Connecting: " + getCurrentMessage().what);
-            }
+            logD("Exit Connecting: " + getCurrentMessage().what);
             removeMessages(CONNECTING_TIMEOUT);
             mPrevState = this;
         }
@@ -1106,9 +1060,7 @@
 
         @Override
         public void enter() {
-            if (DBG) {
-                Log.d(TAG, "Enter Connected: " + getCurrentMessage().what);
-            }
+            logD("Enter Connected: " + getCurrentMessage().what);
             mAudioWbs = false;
             mCommandedSpeakerVolume = -1;
             if (mPrevState == mConnecting) {
@@ -1125,9 +1077,7 @@
 
         @Override
         public synchronized boolean processMessage(Message message) {
-            if (DBG) {
-                Log.d(TAG, "Connected process message: " + message.what);
-            }
+            logD("Connected process message: " + message.what);
             if (DBG) {
                 if (mCurrentDevice == null) {
                     Log.e(TAG, "ERROR: mCurrentDevice is null in Connected");
@@ -1142,20 +1092,20 @@
                         // already connected to this device, do nothing
                         break;
                     }
-                    NativeInterface.connectNative(getByteAddress(device));
+                    mNativeInterface.connect(getByteAddress(device));
                     break;
                 case DISCONNECT:
                     BluetoothDevice dev = (BluetoothDevice) message.obj;
                     if (!mCurrentDevice.equals(dev)) {
                         break;
                     }
-                    if (!NativeInterface.disconnectNative(getByteAddress(dev))) {
+                    if (!mNativeInterface.disconnect(getByteAddress(dev))) {
                         Log.e(TAG, "disconnectNative failed for " + dev);
                     }
                     break;
 
                 case CONNECT_AUDIO:
-                    if (!NativeInterface.connectAudioNative(getByteAddress(mCurrentDevice))) {
+                    if (!mNativeInterface.connectAudio(getByteAddress(mCurrentDevice))) {
                         Log.e(TAG, "ERROR: Couldn't connect Audio for device " + mCurrentDevice);
                         // No state transition is involved, fire broadcast immediately
                         broadcastAudioState(mCurrentDevice,
@@ -1167,14 +1117,14 @@
                     break;
 
                 case DISCONNECT_AUDIO:
-                    if (!NativeInterface.disconnectAudioNative(getByteAddress(mCurrentDevice))) {
+                    if (!mNativeInterface.disconnectAudio(getByteAddress(mCurrentDevice))) {
                         Log.e(TAG, "ERROR: Couldn't disconnect Audio for device " + mCurrentDevice);
                     }
                     break;
 
                 case VOICE_RECOGNITION_START:
                     if (mVoiceRecognitionActive == HeadsetClientHalConstants.VR_STATE_STOPPED) {
-                        if (NativeInterface.startVoiceRecognitionNative(
+                        if (mNativeInterface.startVoiceRecognition(
                                     getByteAddress(mCurrentDevice))) {
                             addQueuedAction(VOICE_RECOGNITION_START);
                         } else {
@@ -1185,7 +1135,7 @@
 
                 case VOICE_RECOGNITION_STOP:
                     if (mVoiceRecognitionActive == HeadsetClientHalConstants.VR_STATE_STARTED) {
-                        if (NativeInterface.stopVoiceRecognitionNative(
+                        if (mNativeInterface.stopVoiceRecognition(
                                     getByteAddress(mCurrentDevice))) {
                             addQueuedAction(VOICE_RECOGNITION_STOP);
                         } else {
@@ -1194,6 +1144,13 @@
                     }
                     break;
 
+                case SEND_VENDOR_AT_COMMAND: {
+                    int vendorId = message.arg1;
+                    String atCommand = (String) (message.obj);
+                    mVendorProcessor.sendCommand(vendorId, atCommand, mCurrentDevice);
+                    break;
+                }
+
                 // Called only for Mute/Un-mute - Mic volume change is not allowed.
                 case SET_MIC_VOLUME:
                     break;
@@ -1202,10 +1159,10 @@
                     int amVol = message.arg1;
                     int hfVol = amToHfVol(amVol);
                     if (amVol != mCommandedSpeakerVolume) {
-                        Log.d(TAG, "Volume" + amVol + ":" + mCommandedSpeakerVolume);
+                        logD("Volume" + amVol + ":" + mCommandedSpeakerVolume);
                         // Volume was changed by a 3rd party
                         mCommandedSpeakerVolume = -1;
-                        if (NativeInterface.setVolumeNative(getByteAddress(mCurrentDevice),
+                        if (mNativeInterface.setVolume(getByteAddress(mCurrentDevice),
                                 HeadsetClientHalConstants.VOLUME_TYPE_SPK, hfVol)) {
                             addQueuedAction(SET_SPEAKER_VOLUME);
                         }
@@ -1216,7 +1173,7 @@
                     BluetoothHeadsetClientCall c = (BluetoothHeadsetClientCall) message.obj;
                     mCalls.put(HF_ORIGINATED_CALL_ID, c);
 
-                    if (NativeInterface.dialNative(getByteAddress(mCurrentDevice), c.getNumber())) {
+                    if (mNativeInterface.dial(getByteAddress(mCurrentDevice), c.getNumber())) {
                         addQueuedAction(DIAL_NUMBER, c.getNumber());
                         // Start looping on calling current calls.
                         sendMessage(QUERY_CURRENT_CALLS);
@@ -1248,7 +1205,7 @@
                     explicitCallTransfer();
                     break;
                 case SEND_DTMF:
-                    if (NativeInterface.sendDtmfNative(getByteAddress(mCurrentDevice),
+                    if (mNativeInterface.sendDtmf(getByteAddress(mCurrentDevice),
                             (byte) message.arg1)) {
                         addQueuedAction(SEND_DTMF);
                     } else {
@@ -1256,7 +1213,7 @@
                     }
                     break;
                 case SUBSCRIBER_INFO:
-                    if (NativeInterface.retrieveSubscriberInfoNative(
+                    if (mNativeInterface.retrieveSubscriberInfo(
                             getByteAddress(mCurrentDevice))) {
                         addQueuedAction(SUBSCRIBER_INFO);
                     } else {
@@ -1274,29 +1231,21 @@
                 case StackEvent.STACK_EVENT:
                     Intent intent = null;
                     StackEvent event = (StackEvent) message.obj;
-                    if (DBG) {
-                        Log.d(TAG, "Connected: event type: " + event.type);
-                    }
+                    logD("Connected: event type: " + event.type);
 
                     switch (event.type) {
                         case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            if (DBG) {
-                                Log.d(TAG, "Connected: Connection state changed: " + event.device
-                                        + ": " + event.valueInt);
-                            }
+                            logD("Connected: Connection state changed: " + event.device
+                                    + ": " + event.valueInt);
                             processConnectionEvent(event.valueInt, event.device);
                             break;
                         case StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
-                            if (DBG) {
-                                Log.d(TAG, "Connected: Audio state changed: " + event.device + ": "
-                                        + event.valueInt);
-                            }
+                            logD("Connected: Audio state changed: " + event.device + ": "
+                                    + event.valueInt);
                             processAudioEvent(event.valueInt, event.device);
                             break;
                         case StackEvent.EVENT_TYPE_NETWORK_STATE:
-                            if (DBG) {
-                                Log.d(TAG, "Connected: Network state: " + event.valueInt);
-                            }
+                            logD("Connected: Network state: " + event.valueInt);
                             mIndicatorNetworkState = event.valueInt;
 
                             intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
@@ -1315,7 +1264,7 @@
 
                             if (mIndicatorNetworkState
                                     == HeadsetClientHalConstants.NETWORK_STATE_AVAILABLE) {
-                                if (NativeInterface.queryCurrentOperatorNameNative(
+                                if (mNativeInterface.queryCurrentOperatorName(
                                         getByteAddress(mCurrentDevice))) {
                                     addQueuedAction(QUERY_OPERATOR_NAME);
                                 } else {
@@ -1388,7 +1337,7 @@
                         case StackEvent.EVENT_TYPE_VOLUME_CHANGED:
                             if (event.valueInt == HeadsetClientHalConstants.VOLUME_TYPE_SPK) {
                                 mCommandedSpeakerVolume = hfToAmVol(event.valueInt2);
-                                Log.d(TAG, "AM volume set to " + mCommandedSpeakerVolume);
+                                logD("AM volume set to " + mCommandedSpeakerVolume);
                                 mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
                                         +mCommandedSpeakerVolume, AudioManager.FLAG_SHOW_UI);
                             } else if (event.valueInt
@@ -1405,10 +1354,8 @@
                                 break;
                             }
 
-                            if (DBG) {
-                                Log.d(TAG, "Connected: command result: " + event.valueInt
-                                        + " queuedAction: " + queuedAction.first);
-                            }
+                            logD("Connected: command result: " + event.valueInt
+                                    + " queuedAction: " + queuedAction.first);
 
                             switch (queuedAction.first) {
                                 case QUERY_CURRENT_CALLS:
@@ -1447,16 +1394,19 @@
                                     event.valueInt);
                             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
                             mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-                            if (DBG) {
-                                Log.d(TAG,
-                                        event.device.toString() + "onInBandRing" + event.valueInt);
-                            }
+                            logD(event.device.toString() + "onInBandRing" + event.valueInt);
                             break;
                         case StackEvent.EVENT_TYPE_RING_INDICATION:
                             // Ringing is not handled at this indication and rather should be
                             // implemented (by the client of this service). Use the
                             // CALL_STATE_INCOMING (and similar) handle ringing.
                             break;
+                        case StackEvent.EVENT_TYPE_UNKNOWN_EVENT:
+                            if (!mVendorProcessor.processEvent(event.valueString, event.device)) {
+                                Log.e(TAG, "Unknown event :" + event.valueString
+                                        + " for device " + event.device);
+                            }
+                            break;
                         default:
                             Log.e(TAG, "Unknown stack event: " + event.type);
                             break;
@@ -1473,9 +1423,7 @@
         private void processConnectionEvent(int state, BluetoothDevice device) {
             switch (state) {
                 case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
-                    if (DBG) {
-                        Log.d(TAG, "Connected disconnects.");
-                    }
+                    logD("Connected disconnects.");
                     // AG disconnects
                     if (mCurrentDevice.equals(device)) {
                         transitionTo(mDisconnected);
@@ -1526,23 +1474,15 @@
                     final int amVol = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
                     final int hfVol = amToHfVol(amVol);
 
-                    if (DBG) {
-                        Log.d(TAG, "hfp_enable=true mAudioWbs is " + mAudioWbs);
-                    }
+                    logD("hfp_enable=true mAudioWbs is " + mAudioWbs);
                     if (mAudioWbs) {
-                        if (DBG) {
-                            Log.d(TAG, "Setting sampling rate as 16000");
-                        }
+                        logD("Setting sampling rate as 16000");
                         mAudioManager.setParameters("hfp_set_sampling_rate=16000");
                     } else {
-                        if (DBG) {
-                            Log.d(TAG, "Setting sampling rate as 8000");
-                        }
+                        logD("Setting sampling rate as 8000");
                         mAudioManager.setParameters("hfp_set_sampling_rate=8000");
                     }
-                    if (DBG) {
-                        Log.d(TAG, "hf_volume " + hfVol);
-                    }
+                    logD("hf_volume " + hfVol);
                     routeHfpAudio(true);
                     mAudioFocusRequest = requestAudioFocus();
                     mAudioManager.setParameters("hfp_volume=" + hfVol);
@@ -1571,9 +1511,7 @@
 
         @Override
         public void exit() {
-            if (DBG) {
-                Log.d(TAG, "Exit Connected: " + getCurrentMessage().what);
-            }
+            logD("Exit Connected: " + getCurrentMessage().what);
             mPrevState = this;
         }
     }
@@ -1581,18 +1519,14 @@
     class AudioOn extends State {
         @Override
         public void enter() {
-            if (DBG) {
-                Log.d(TAG, "Enter AudioOn: " + getCurrentMessage().what);
-            }
+            logD("Enter AudioOn: " + getCurrentMessage().what);
             broadcastAudioState(mCurrentDevice, BluetoothHeadsetClient.STATE_AUDIO_CONNECTED,
                     BluetoothHeadsetClient.STATE_AUDIO_CONNECTING);
         }
 
         @Override
         public synchronized boolean processMessage(Message message) {
-            if (DBG) {
-                Log.d(TAG, "AudioOn process message: " + message.what);
-            }
+            logD("AudioOn process message: " + message.what);
             if (DBG) {
                 if (mCurrentDevice == null) {
                     Log.e(TAG, "ERROR: mCurrentDevice is null in Connected");
@@ -1617,7 +1551,7 @@
                      * StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, that triggers State
                      * Machines state changing
                      */
-                    if (NativeInterface.disconnectAudioNative(getByteAddress(mCurrentDevice))) {
+                    if (mNativeInterface.disconnectAudio(getByteAddress(mCurrentDevice))) {
                         routeHfpAudio(false);
                         returnAudioFocusIfNecessary();
                     }
@@ -1629,22 +1563,16 @@
 
                 case StackEvent.STACK_EVENT:
                     StackEvent event = (StackEvent) message.obj;
-                    if (DBG) {
-                        Log.d(TAG, "AudioOn: event type: " + event.type);
-                    }
+                    logD("AudioOn: event type: " + event.type);
                     switch (event.type) {
                         case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            if (DBG) {
-                                Log.d(TAG, "AudioOn connection state changed" + event.device + ": "
-                                        + event.valueInt);
-                            }
+                            logD("AudioOn connection state changed" + event.device + ": "
+                                    + event.valueInt);
                             processConnectionEvent(event.valueInt, event.device);
                             break;
                         case StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
-                            if (DBG) {
-                                Log.d(TAG, "AudioOn audio state changed" + event.device + ": "
-                                        + event.valueInt);
-                            }
+                            logD("AudioOn audio state changed" + event.device + ": "
+                                    + event.valueInt);
                             processAudioEvent(event.valueInt, event.device);
                             break;
                         default:
@@ -1703,9 +1631,7 @@
 
         @Override
         public void exit() {
-            if (DBG) {
-                Log.d(TAG, "Exit AudioOn: " + getCurrentMessage().what);
-            }
+            logD("Exit AudioOn: " + getCurrentMessage().what);
             mPrevState = this;
             broadcastAudioState(mCurrentDevice, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
                     BluetoothHeadsetClient.STATE_AUDIO_CONNECTED);
@@ -1738,6 +1664,11 @@
     }
 
     private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) {
+        StatsLog.write(StatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED,
+                AdapterService.getAdapterService().obfuscateAddress(device),
+                getConnectionStateFromAudioState(newState), mAudioWbs
+                        ? BluetoothHfpProtoEnums.SCO_CODEC_MSBC
+                        : BluetoothHfpProtoEnums.SCO_CODEC_CVSD);
         Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
         intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
@@ -1746,16 +1677,12 @@
         }
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-        if (DBG) {
-            Log.d(TAG, "Audio state " + device + ": " + prevState + "->" + newState);
-        }
+        logD("Audio state " + device + ": " + prevState + "->" + newState);
     }
 
     // This method does not check for error condition (newState == prevState)
     private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
-        if (DBG) {
-            Log.d(TAG, "Connection state " + device + ": " + prevState + "->" + newState);
-        }
+        logD("Connection state " + device + ": " + prevState + "->" + newState);
         /*
          * Notifying the connection state change of the profile before sending
          * the intent for connection state change, as it was causing a race
@@ -1773,6 +1700,10 @@
                     == HeadsetClientHalConstants.PEER_FEAT_3WAY) {
                 intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
             }
+            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_VREC)
+                    == HeadsetClientHalConstants.PEER_FEAT_VREC) {
+                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION, true);
+            }
             if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)
                     == HeadsetClientHalConstants.PEER_FEAT_REJECT) {
                 intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
@@ -1890,4 +1821,22 @@
         b.putString(BluetoothHeadsetClient.EXTRA_SUBSCRIBER_INFO, mSubscriberInfo);
         return b;
     }
+
+    private static int getConnectionStateFromAudioState(int audioState) {
+        switch (audioState) {
+            case BluetoothHeadsetClient.STATE_AUDIO_CONNECTED:
+                return BluetoothAdapter.STATE_CONNECTED;
+            case BluetoothHeadsetClient.STATE_AUDIO_CONNECTING:
+                return BluetoothAdapter.STATE_CONNECTING;
+            case BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED:
+                return BluetoothAdapter.STATE_DISCONNECTED;
+        }
+        return BluetoothAdapter.STATE_DISCONNECTED;
+    }
+
+    private static void logD(String message) {
+        if (DBG) {
+            Log.d(TAG, message);
+        }
+    }
 }
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineFactory.java b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineFactory.java
index 6be72d0..b0c7265 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineFactory.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineFactory.java
@@ -20,7 +20,12 @@
 
 // Factory so that StateMachine objected can be mocked
 public class HeadsetClientStateMachineFactory {
-    public HeadsetClientStateMachine make(HeadsetClientService context, HandlerThread t) {
-        return HeadsetClientStateMachine.make(context, t.getLooper());
+    /**
+     * Factory method to create state machine for headset client
+     *
+     */
+    public HeadsetClientStateMachine make(HeadsetClientService context, HandlerThread t,
+            NativeInterface nativeInterface) {
+        return HeadsetClientStateMachine.make(context, t.getLooper(), nativeInterface);
     }
 }
diff --git a/src/com/android/bluetooth/hfpclient/NativeInterface.java b/src/com/android/bluetooth/hfpclient/NativeInterface.java
index 77d9af7..d622e78 100644
--- a/src/com/android/bluetooth/hfpclient/NativeInterface.java
+++ b/src/com/android/bluetooth/hfpclient/NativeInterface.java
@@ -24,50 +24,280 @@
 import android.bluetooth.BluetoothDevice;
 import android.util.Log;
 
-class NativeInterface {
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Defines native calls that are used by state machine/service to either send or receive
+ * messages to/from the native stack. This file is registered for the native methods in
+ * corresponding CPP file.
+ */
+public class NativeInterface {
     private static final String TAG = "NativeInterface";
     private static final boolean DBG = false;
 
-    NativeInterface() {}
+    static {
+        classInitNative();
+    }
+
+    private NativeInterface() {}
+    private static NativeInterface sInterface;
+    private static final Object INSTANCE_LOCK = new Object();
+
+    /**
+     * This class is a singleton because native library should only be loaded once
+     *
+     * @return default instance
+     */
+    public static NativeInterface getInstance() {
+        synchronized (INSTANCE_LOCK) {
+            if (sInterface == null) {
+                sInterface = new NativeInterface();
+            }
+        }
+        return sInterface;
+    }
+
+    // Native wrappers to help unit testing
+    /**
+     * Initialize native stack
+     */
+    @VisibleForTesting
+    public void initialize() {
+        initializeNative();
+    }
+
+    /**
+     * Close and clean up native stack
+     */
+    @VisibleForTesting
+    public void cleanup() {
+        cleanupNative();
+    }
+
+    /**
+     * Connect to the specified paired device
+     *
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean connect(byte[] address) {
+        return connectNative(address);
+    }
+
+    /**
+     * Disconnect from the specified paired device
+     *
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean disconnect(byte[] address) {
+        return disconnectNative(address);
+    }
+
+    /**
+     * Initiate audio connection to the specified paired device
+     *
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean connectAudio(byte[] address) {
+        return connectAudioNative(address);
+    }
+
+    /**
+     * Close audio connection from the specified paired device
+     *
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    public boolean disconnectAudio(byte[] address) {
+        return disconnectAudioNative(address);
+    }
+
+    /**
+     * Initiate voice recognition to the specified paired device
+     *
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean startVoiceRecognition(byte[] address) {
+        return startVoiceRecognitionNative(address);
+    }
+
+    /**
+     * Close voice recognition to the specified paired device
+     *
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean stopVoiceRecognition(byte[] address) {
+        return stopVoiceRecognitionNative(address);
+    }
+
+    /**
+     * Set volume to the specified paired device
+     *
+     * @param volumeType type of volume as in
+     *                  HeadsetClientHalConstants.VOLUME_TYPE_xxxx
+     * @param volume  volume level
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean setVolume(byte[] address, int volumeType, int volume) {
+        return setVolumeNative(address, volumeType, volume);
+    }
+
+    /**
+     * dial number from the specified paired device
+     *
+     * @param number  phone number to be dialed
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean dial(byte[] address, String number) {
+        return dialNative(address, number);
+    }
+
+    /**
+     * Memory dialing from the specified paired device
+     *
+     * @param location  memory location
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean dialMemory(byte[] address, int location) {
+        return dialMemoryNative(address, location);
+    }
+
+    /**
+     * Apply action to call
+     *
+     * @action action (e.g. hold, terminate etc)
+     * @index call index
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean handleCallAction(byte[] address, int action, int index) {
+        return handleCallActionNative(address, action, index);
+    }
+
+    /**
+     * Query current call status from the specified paired device
+     *
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean queryCurrentCalls(byte[] address) {
+        return queryCurrentCallsNative(address);
+    }
+
+    /**
+     * Query operator name from the specified paired device
+     *
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean queryCurrentOperatorName(byte[] address) {
+        return queryCurrentOperatorNameNative(address);
+    }
+
+    /**
+     * Retrieve subscriber number from the specified paired device
+     *
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public  boolean retrieveSubscriberInfo(byte[] address) {
+        return retrieveSubscriberInfoNative(address);
+    }
+
+    /**
+     * Transmit DTMF code
+     *
+     * @param code DTMF code
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean sendDtmf(byte[] address, byte code) {
+        return sendDtmfNative(address, code);
+    }
+
+    /**
+     * Request last voice tag
+     *
+     * @param address target device's address
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean requestLastVoiceTagNumber(byte[] address) {
+        return requestLastVoiceTagNumberNative(address);
+    }
+
+    /**
+     * Send an AT command
+     *
+     * @param atCmd command code
+     * @param val1 command specific argurment1
+     * @param val2 command specific argurment2
+     * @param arg other command specific argurments
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean sendATCmd(byte[] address, int atCmd, int val1, int val2, String arg) {
+        return sendATCmdNative(address, atCmd, val1, val2, arg);
+    }
 
     // Native methods that call into the JNI interface
-    static native void classInitNative();
+    private static native void classInitNative();
 
-    static native void initializeNative();
+    private native void initializeNative();
 
-    static native void cleanupNative();
+    private native void cleanupNative();
 
-    static native boolean connectNative(byte[] address);
+    private static native boolean connectNative(byte[] address);
 
-    static native boolean disconnectNative(byte[] address);
+    private static native boolean disconnectNative(byte[] address);
 
-    static native boolean connectAudioNative(byte[] address);
+    private static native boolean connectAudioNative(byte[] address);
 
-    static native boolean disconnectAudioNative(byte[] address);
+    private static native boolean disconnectAudioNative(byte[] address);
 
-    static native boolean startVoiceRecognitionNative(byte[] address);
+    private static native boolean startVoiceRecognitionNative(byte[] address);
 
-    static native boolean stopVoiceRecognitionNative(byte[] address);
+    private static native boolean stopVoiceRecognitionNative(byte[] address);
 
-    static native boolean setVolumeNative(byte[] address, int volumeType, int volume);
+    private static native boolean setVolumeNative(byte[] address, int volumeType, int volume);
 
-    static native boolean dialNative(byte[] address, String number);
+    private static native boolean dialNative(byte[] address, String number);
 
-    static native boolean dialMemoryNative(byte[] address, int location);
+    private static native boolean dialMemoryNative(byte[] address, int location);
 
-    static native boolean handleCallActionNative(byte[] address, int action, int index);
+    private static native boolean handleCallActionNative(byte[] address, int action, int index);
 
-    static native boolean queryCurrentCallsNative(byte[] address);
+    private static native boolean queryCurrentCallsNative(byte[] address);
 
-    static native boolean queryCurrentOperatorNameNative(byte[] address);
+    private static native boolean queryCurrentOperatorNameNative(byte[] address);
 
-    static native boolean retrieveSubscriberInfoNative(byte[] address);
+    private static native boolean retrieveSubscriberInfoNative(byte[] address);
 
-    static native boolean sendDtmfNative(byte[] address, byte code);
+    private static native boolean sendDtmfNative(byte[] address, byte code);
 
-    static native boolean requestLastVoiceTagNumberNative(byte[] address);
+    private static native boolean requestLastVoiceTagNumberNative(byte[] address);
 
-    static native boolean sendATCmdNative(byte[] address, int atCmd, int val1, int val2,
+    private static native boolean sendATCmdNative(byte[] address, int atCmd, int val1, int val2,
             String arg);
 
     private BluetoothDevice getDevice(byte[] address) {
@@ -420,4 +650,21 @@
                     "onRingIndication: Ignoring message because service not available: " + event);
         }
     }
+
+    private void onUnknownEvent(String eventString, byte[] address) {
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
+        event.device = getDevice(address);
+        event.valueString = eventString;
+        if (DBG) {
+            Log.d(TAG, "onUnknownEvent: address " + address + " event " + event);
+        }
+        HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
+        if (service != null) {
+            service.messageFromNative(event);
+        } else {
+            Log.w(TAG,
+                    "onUnknowEvent: Ignoring message because service not available: " + event);
+        }
+    }
+
 }
diff --git a/src/com/android/bluetooth/hfpclient/StackEvent.java b/src/com/android/bluetooth/hfpclient/StackEvent.java
index de2a338..e9f7cba 100644
--- a/src/com/android/bluetooth/hfpclient/StackEvent.java
+++ b/src/com/android/bluetooth/hfpclient/StackEvent.java
@@ -47,6 +47,7 @@
     public static final int EVENT_TYPE_SUBSCRIBER_INFO = 18;
     public static final int EVENT_TYPE_IN_BAND_RINGTONE = 19;
     public static final int EVENT_TYPE_RING_INDICATION = 21;
+    public static final int EVENT_TYPE_UNKNOWN_EVENT = 22;
 
     public int type = EVENT_TYPE_NONE;
     public int valueInt = 0;
diff --git a/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java b/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java
new file mode 100644
index 0000000..7e81d71
--- /dev/null
+++ b/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Defines utility inteface that is used by state machine/service to either send vendor specific AT
+ * command or receive vendor specific response from the native stack.
+ */
+package com.android.bluetooth.hfpclient;
+
+import android.bluetooth.BluetoothAssignedNumbers;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+class VendorCommandResponseProcessor {
+
+    private static final String TAG = "VendorCommandResponseProcessor";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final HeadsetClientService mService;
+    private final NativeInterface mNativeInterface;
+
+    // Keys are AT commands (without payload), and values are the company IDs.
+    private static final Map<String, Integer> SUPPORTED_VENDOR_AT_COMMANDS;
+    static {
+        SUPPORTED_VENDOR_AT_COMMANDS = new HashMap<>();
+        SUPPORTED_VENDOR_AT_COMMANDS.put(
+                "+XAPL=",
+                BluetoothAssignedNumbers.APPLE);
+        SUPPORTED_VENDOR_AT_COMMANDS.put(
+                "+IPHONEACCEV=",
+                BluetoothAssignedNumbers.APPLE);
+        SUPPORTED_VENDOR_AT_COMMANDS.put(
+                "+APLSIRI?",
+                BluetoothAssignedNumbers.APPLE);
+        SUPPORTED_VENDOR_AT_COMMANDS.put(
+                "+APLEFM",
+                BluetoothAssignedNumbers.APPLE);
+    }
+
+    // Keys are AT events (without payload), and values are the company IDs.
+    private static final Map<String, Integer> SUPPORTED_VENDOR_EVENTS;
+    static {
+        SUPPORTED_VENDOR_EVENTS = new HashMap<>();
+        SUPPORTED_VENDOR_EVENTS.put(
+                "+APLSIRI:",
+                BluetoothAssignedNumbers.APPLE);
+        SUPPORTED_VENDOR_EVENTS.put(
+                "+XAPL=",
+                BluetoothAssignedNumbers.APPLE);
+    }
+
+    VendorCommandResponseProcessor(HeadsetClientService context, NativeInterface nativeInterface) {
+        mService = context;
+        mNativeInterface = nativeInterface;
+    }
+
+    public boolean sendCommand(int vendorId, String atCommand, BluetoothDevice device) {
+        if (device == null) {
+            Log.w(TAG, "processVendorCommand device is null");
+            return false;
+        }
+
+        // Do not support more than one command at one line.
+        // We simplify and say no ; allowed as well.
+        int indexOfSemicolon = atCommand.indexOf(';');
+        if (indexOfSemicolon > 0) {
+            Log.e(TAG, "Do not support ; and more than one command:"
+                    + atCommand);
+            return false;
+        }
+
+        // Get command word
+        int indexOfEqual = atCommand.indexOf('=');
+        int indexOfQuestionMark = atCommand.indexOf('?');
+        String commandWord;
+        if (indexOfEqual > 0) {
+            commandWord = atCommand.substring(0, indexOfEqual + 1);
+        } else if (indexOfQuestionMark > 0) {
+            commandWord = atCommand.substring(0, indexOfQuestionMark + 1);
+        } else {
+            commandWord = atCommand;
+        }
+
+        // replace all white spaces
+        commandWord = commandWord.replaceAll("\\s+", "");
+
+        if (SUPPORTED_VENDOR_AT_COMMANDS.get(commandWord) != (Integer) (vendorId)) {
+            Log.e(TAG, "Invalid command " + atCommand + ", " + vendorId + ". Cand="
+                    + commandWord);
+            return false;
+        }
+
+        if (!mNativeInterface.sendATCmd(Utils.getBytesFromAddress(device.getAddress()),
+                                        HeadsetClientHalConstants
+                                        .HANDSFREECLIENT_AT_CMD_VENDOR_SPECIFIC_CMD,
+                                        0, 0, atCommand)) {
+            Log.e(TAG, "Failed to send vendor specific at command");
+            return false;
+        }
+        logD("Send vendor command: " + atCommand);
+        return true;
+    }
+
+    public boolean processEvent(String atString, BluetoothDevice device) {
+        if (device == null) {
+            Log.w(TAG, "processVendorEvent device is null");
+            return false;
+        }
+
+        // Get event code
+        int indexOfEqual = atString.indexOf('=');
+        int indexOfColon = atString.indexOf(':');
+        String eventCode;
+        if (indexOfEqual > 0) {
+            eventCode = atString.substring(0, indexOfEqual + 1);
+        } else if (indexOfColon > 0) {
+            eventCode = atString.substring(0, indexOfColon + 1);
+        } else {
+            eventCode = atString;
+        }
+
+        // replace all white spaces
+        eventCode = eventCode.replaceAll("\\s+", "");
+
+        Integer vendorId = SUPPORTED_VENDOR_EVENTS.get(eventCode);
+        if (vendorId == null) {
+            Log.e(TAG, "Invalid response: " + atString + ". " + eventCode);
+            return false;
+        }
+        broadcastVendorSpecificEventIntent(vendorId, eventCode, atString, device);
+        logD("process vendor event " + vendorId + ", " + eventCode + ", "
+                + atString + " for device" + device);
+        return true;
+    }
+
+    private void broadcastVendorSpecificEventIntent(int vendorId, String vendorEventCode,
+            String vendorResponse, BluetoothDevice device) {
+        logD("broadcastVendorSpecificEventIntent(" + vendorResponse + ")");
+        Intent intent = new Intent(BluetoothHeadsetClient
+                                   .ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT);
+        intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_ID, vendorId);
+        intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_CODE, vendorEventCode);
+        intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_FULL_ARGS, vendorResponse);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        mService.sendBroadcast(intent, HeadsetClientService.BLUETOOTH_PERM);
+    }
+
+    private void logD(String msg) {
+        if (DBG) {
+            Log.d(TAG, msg);
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
index 1d2add4..f3cb472 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
@@ -80,7 +80,6 @@
             return;
         }
 
-        mHeadsetProfile.connectAudio(device);
         setInitializing();
         setDialing();
         finishInitializing();
@@ -265,7 +264,6 @@
         if (!mClosed) {
             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
         }
-        mHeadsetProfile.connectAudio(mDevice);
     }
 
     @Override
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
index af46122..2abff9c 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
@@ -328,10 +328,19 @@
         PhoneAccountHandle handle =
                 new PhoneAccountHandle(new ComponentName(context, HfpClientConnectionService.class),
                         device.getAddress());
+
+        int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER;
+        if (context.getApplicationContext().getResources().getBoolean(
+                com.android.bluetooth.R.bool
+                .hfp_client_connection_service_support_emergency_call)) {
+            // Need to have an emergency call capability to place emergency call
+            capabilities |= PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS;
+        }
+
         PhoneAccount account =
                 new PhoneAccount.Builder(handle, "HFP " + device.toString()).setAddress(addr)
                         .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
-                        .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                        .setCapabilities(capabilities)
                         .build();
         if (DBG) {
             Log.d(TAG, "phoneaccount: " + account);
diff --git a/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java b/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
index c0f7042..db72a59 100644
--- a/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
+++ b/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
@@ -24,11 +24,11 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import com.android.bluetooth.Utils;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * HID Device Native Interface to/from JNI.
diff --git a/src/com/android/bluetooth/hid/HidHostService.java b/src/com/android/bluetooth/hid/HidHostService.java
index 63f5206..7bc7216 100644
--- a/src/com/android/bluetooth/hid/HidHostService.java
+++ b/src/com/android/bluetooth/hid/HidHostService.java
@@ -26,9 +26,10 @@
 import android.os.Message;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
@@ -181,7 +182,7 @@
                         if (DBG) {
                             Log.d(TAG, "Incoming HID connection rejected");
                         }
-                        disconnectHidNative(Utils.getByteAddress(device));
+                        virtualUnPlugNative(Utils.getByteAddress(device));
                     } else {
                         broadcastConnectionState(device, convertHalState(halState));
                     }
@@ -523,11 +524,11 @@
         if (DBG) {
             Log.d(TAG, "setPriority: " + device.getAddress());
         }
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothHidHostPriorityKey(device.getAddress()), priority);
         if (DBG) {
             Log.d(TAG, "Saved priority " + device + " = " + priority);
         }
+        AdapterService.getAdapterService().getDatabase()
+                .setProfilePriority(device, BluetoothProfile.HID_HOST, priority);
         return true;
     }
 
@@ -536,10 +537,8 @@
         if (DBG) {
             Log.d(TAG, "getPriority: " + device.getAddress());
         }
-        int priority = Settings.Global.getInt(getContentResolver(),
-                Settings.Global.getBluetoothHidHostPriorityKey(device.getAddress()),
-                BluetoothProfile.PRIORITY_UNDEFINED);
-        return priority;
+        return AdapterService.getAdapterService().getDatabase()
+                .getProfilePriority(device, BluetoothProfile.HID_HOST);
     }
 
     /* The following APIs regarding test app for compliance */
diff --git a/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java b/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
index 3ad600b..1fc4af9 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
@@ -185,7 +185,7 @@
             return children;
         } finally {
             if (mProviderClient != null) {
-                mProviderClient.release();
+                mProviderClient.close();
             }
         }
 
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index c862619..8a73ef2 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -1054,7 +1054,7 @@
         mResolver.unregisterContentObserver(mObserver);
         mObserverRegistered = false;
         if (mProviderClient != null) {
-            mProviderClient.release();
+            mProviderClient.close();
             mProviderClient = null;
         }
     }
diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index afd1eb0..3b35262 100644
--- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -974,7 +974,7 @@
 
         }
         if (mProviderClient != null) {
-            mProviderClient.release();
+            mProviderClient.close();
             mProviderClient = null;
         }
 
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index bfb1d3f..cc42b60 100644
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -36,8 +36,6 @@
 import android.os.ParcelUuid;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
@@ -45,8 +43,10 @@
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -577,14 +577,14 @@
         if (VERBOSE) {
             Log.v(TAG, "Saved priority " + device + " = " + priority);
         }
-        return Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothMapPriorityKey(device.getAddress()), priority);
+        AdapterService.getAdapterService().getDatabase()
+                .setProfilePriority(device, BluetoothProfile.MAP, priority);
+        return true;
     }
 
     int getPriority(BluetoothDevice device) {
-        return Settings.Global.getInt(getContentResolver(),
-                Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
-                BluetoothProfile.PRIORITY_UNDEFINED);
+        return AdapterService.getAdapterService().getDatabase()
+                .getProfilePriority(device, BluetoothProfile.MAP);
     }
 
     @Override
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/MapClientService.java b/src/com/android/bluetooth/mapclient/MapClientService.java
index 0d77c8e..9467683 100644
--- a/src/com/android/bluetooth/mapclient/MapClientService.java
+++ b/src/com/android/bluetooth/mapclient/MapClientService.java
@@ -30,12 +30,12 @@
 import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.ParcelUuid;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -101,6 +101,10 @@
             Log.d(TAG, "MAP connect device: " + device
                     + ", InstanceMap start state: " + sb.toString());
         }
+        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
+            Log.w(TAG, "Connection not allowed: <" + device.getAddress() + "> is PRIORITY_OFF");
+            return false;
+        }
         MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
         if (mapStateMachine == null) {
             // a map state machine instance doesn't exist yet, create a new one if we can.
@@ -189,20 +193,20 @@
     }
 
     public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
+        if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
         List<BluetoothDevice> deviceList = new ArrayList<>();
         Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
         int connectionState;
         for (BluetoothDevice device : bondedDevices) {
             connectionState = getConnectionState(device);
-            Log.d(TAG, "Device: " + device + "State: " + connectionState);
+            if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState);
             for (int i = 0; i < states.length; i++) {
                 if (connectionState == states[i]) {
                     deviceList.add(device);
                 }
             }
         }
-        Log.d(TAG, deviceList.toString());
+        if (DBG) Log.d(TAG, deviceList.toString());
         return deviceList;
     }
 
@@ -214,19 +218,17 @@
     }
 
     public boolean setPriority(BluetoothDevice device, int priority) {
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothMapClientPriorityKey(device.getAddress()), priority);
         if (VDBG) {
             Log.v(TAG, "Saved priority " + device + " = " + priority);
         }
+        AdapterService.getAdapterService().getDatabase()
+                .setProfilePriority(device, BluetoothProfile.MAP_CLIENT, priority);
         return true;
     }
 
     public int getPriority(BluetoothDevice device) {
-        int priority = Settings.Global.getInt(getContentResolver(),
-                Settings.Global.getBluetoothMapClientPriorityKey(device.getAddress()),
-                BluetoothProfile.PRIORITY_UNDEFINED);
-        return priority;
+        return AdapterService.getAdapterService().getDatabase()
+                .getProfilePriority(device, BluetoothProfile.MAP_CLIENT);
     }
 
     public synchronized boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
@@ -293,7 +295,13 @@
         setMapClientService(null);
     }
 
-    void cleanupDevice(BluetoothDevice device) {
+    /**
+     * cleanupDevice removes the associated state machine from the instance map
+     *
+     * @param device BluetoothDevice address of remote device
+     */
+    @VisibleForTesting
+    public void cleanupDevice(BluetoothDevice device) {
         if (DBG) {
             StringBuilder sb = new StringBuilder();
             dump(sb);
@@ -346,10 +354,23 @@
         return mapStateMachine.getUnreadMessages();
     }
 
+    /**
+     * Returns the SDP record's MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114).
+     * @param device The Bluetooth device to get this value for.
+     * @return the SDP record's MapSupportedFeatures field.
+     */
+    public synchronized int getSupportedFeatures(BluetoothDevice device) {
+        MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
+        if (mapStateMachine == null) {
+            if (DBG) Log.d(TAG, "in getSupportedFeatures, returning 0");
+            return 0;
+        }
+        return mapStateMachine.getSupportedFeatures();
+    }
+
     @Override
     public void dump(StringBuilder sb) {
         super.dump(sb);
-        ProfileService.println(sb, "# Services Connected: " + mMapInstanceMap.size());
         for (MceStateMachine stateMachine : mMapInstanceMap.values()) {
             stateMachine.dump(sb);
         }
@@ -487,7 +508,7 @@
             if (service == null) {
                 return false;
             }
-            Log.d(TAG, "Checking Permission of sendMessage");
+            if (DBG) Log.d(TAG, "Checking Permission of sendMessage");
             mService.enforceCallingOrSelfPermission(Manifest.permission.SEND_SMS,
                     "Need SEND_SMS permission");
 
@@ -504,6 +525,21 @@
                     "Need READ_SMS permission");
             return service.getUnreadMessages(device);
         }
+
+        @Override
+        public int getSupportedFeatures(BluetoothDevice device) {
+            MapClientService service = getService();
+            if (service == null) {
+                if (DBG) {
+                    Log.d(TAG,
+                            "in MapClientService getSupportedFeatures stub, returning 0");
+                }
+                return 0;
+            }
+            mService.enforceCallingOrSelfPermission(Manifest.permission.BLUETOOTH,
+                    "Need BLUETOOTH permission");
+            return service.getSupportedFeatures(device);
+        }
     }
 
     private class MapBroadcastReceiver extends BroadcastReceiver {
diff --git a/src/com/android/bluetooth/mapclient/MapUtils.java b/src/com/android/bluetooth/mapclient/MapUtils.java
index 4ed26c8..b0a603b 100644
--- a/src/com/android/bluetooth/mapclient/MapUtils.java
+++ b/src/com/android/bluetooth/mapclient/MapUtils.java
@@ -15,7 +15,7 @@
  */
 package com.android.bluetooth.mapclient;
 
-import android.support.annotation.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting;
 
 class MapUtils {
     private static MnsService sMnsService = null;
diff --git a/src/com/android/bluetooth/mapclient/MasClient.java b/src/com/android/bluetooth/mapclient/MasClient.java
index d7283bf..6991704 100644
--- a/src/com/android/bluetooth/mapclient/MasClient.java
+++ b/src/com/android/bluetooth/mapclient/MasClient.java
@@ -104,7 +104,7 @@
                         + mSdpMasRecord.getRfcommCannelNumber());
             }
             mSocket = mRemoteDevice.createRfcommSocket(mSdpMasRecord.getRfcommCannelNumber());
-            Log.d(TAG, mRemoteDevice.toString() + "Socket: " + mSocket.toString());
+            if (DBG) Log.d(TAG, mRemoteDevice.toString() + "Socket: " + mSocket.toString());
             mSocket.connect();
             mTransport = new BluetoothObexTransport(mSocket);
 
@@ -118,7 +118,7 @@
             oap.addToHeaderSet(headerset);
 
             headerset = mSession.connect(headerset);
-            Log.d(TAG, "Connection results" + headerset.getResponseCode());
+            if (DBG) Log.d(TAG, "Connection results" + headerset.getResponseCode());
 
             if (headerset.getResponseCode() == ResponseCodes.OBEX_HTTP_OK) {
                 if (DBG) {
@@ -190,6 +190,10 @@
         NATIVE, UTF_8;
     }
 
+    SdpMasRecord getSdpMasRecord() {
+        return mSdpMasRecord;
+    }
+
     private static class MasClientHandler extends Handler {
         WeakReference<MasClient> mInst;
 
@@ -201,22 +205,23 @@
         @Override
         public void handleMessage(Message msg) {
             MasClient inst = mInst.get();
-            if (!inst.mConnected && msg.what != CONNECT) {
-                Log.w(TAG, "Cannot execute " + msg + " when not CONNECTED.");
-                return;
-            }
-
             switch (msg.what) {
                 case CONNECT:
-                    inst.connect();
+                    if (!inst.mConnected) {
+                        inst.connect();
+                    }
                     break;
 
                 case DISCONNECT:
-                    inst.disconnect();
+                    if (inst.mConnected) {
+                        inst.disconnect();
+                    }
                     break;
 
                 case REQUEST:
-                    inst.executeRequest((Request) msg.obj);
+                    if (inst.mConnected) {
+                        inst.executeRequest((Request) msg.obj);
+                    }
                     break;
             }
         }
diff --git a/src/com/android/bluetooth/mapclient/MceStateMachine.java b/src/com/android/bluetooth/mapclient/MceStateMachine.java
index ae11d2a..867ab18 100644
--- a/src/com/android/bluetooth/mapclient/MceStateMachine.java
+++ b/src/com/android/bluetooth/mapclient/MceStateMachine.java
@@ -42,7 +42,6 @@
 
 import android.app.Activity;
 import android.app.PendingIntent;
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothMapClient;
 import android.bluetooth.BluetoothProfile;
@@ -51,6 +50,7 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Message;
+import android.provider.Telephony;
 import android.telecom.PhoneAccount;
 import android.telephony.SmsManager;
 import android.util.Log;
@@ -70,6 +70,7 @@
 import java.util.Calendar;
 import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
 
 /* The MceStateMachine is responsible for setting up and maintaining a connection to a single
  * specific Messaging Server Equipment endpoint.  Upon connect command an SDP record is retrieved,
@@ -122,6 +123,48 @@
             new HashMap<>(MAX_MESSAGES);
     private Bmessage.Type mDefaultMessageType = Bmessage.Type.SMS_CDMA;
 
+    /**
+     * An object to hold the necessary meta-data for each message so we can broadcast it alongside
+     * the message content.
+     *
+     * This is necessary because the metadata is inferred or received separately from the actual
+     * message content.
+     *
+     * Note: In the future it may be best to use the entries from the MessageListing in full instead
+     * of this small subset.
+     */
+    private class MessageMetadata {
+        private final String mHandle;
+        private final Long mTimestamp;
+        private boolean mRead;
+
+        MessageMetadata(String handle, Long timestamp, boolean read) {
+            mHandle = handle;
+            mTimestamp = timestamp;
+            mRead = read;
+        }
+
+        public String getHandle() {
+            return mHandle;
+        }
+
+        public Long getTimestamp() {
+            return mTimestamp;
+        }
+
+        public synchronized boolean getRead() {
+            return mRead;
+        }
+
+        public synchronized void setRead(boolean read) {
+            mRead = read;
+        }
+    }
+
+    // Map each message to its metadata via the handle
+    private ConcurrentHashMap<String, MessageMetadata> mMessages =
+            new ConcurrentHashMap<String, MessageMetadata>();
+
     MceStateMachine(MapClientService service, BluetoothDevice device) {
         this(service, device, null);
     }
@@ -184,7 +227,7 @@
 
     public synchronized int getState() {
         IState currentState = this.getCurrentState();
-        if (currentState.getClass() == Disconnected.class) {
+        if (currentState == null || currentState.getClass() == Disconnected.class) {
             return BluetoothProfile.STATE_DISCONNECTED;
         }
         if (currentState.getClass() == Connected.class) {
@@ -280,6 +323,15 @@
         return false;
     }
 
+    synchronized int getSupportedFeatures() {
+        if (this.getCurrentState() == mConnected && mMasClient != null) {
+            if (DBG) Log.d(TAG, "returning getSupportedFeatures from SDP record");
+            return mMasClient.getSdpMasRecord().getSupportedFeatures();
+        }
+        if (DBG) Log.d(TAG, "in getSupportedFeatures, returning 0");
+        return 0;
+    }
+
     private String getContactURIFromPhone(String number) {
         return PhoneAccount.SCHEME_TEL + ":" + number;
     }
@@ -302,8 +354,8 @@
     }
 
     public void dump(StringBuilder sb) {
-        ProfileService.println(sb, "mCurrentDevice: " + mDevice.getAddress() + " (name = "
-                + mDevice.getName() + "), StateMachine: " + this.toString());
+        ProfileService.println(sb, "mCurrentDevice: " + mDevice.getAddress() + "("
+                + mDevice.getName() + ") " + this.toString());
     }
 
     class Disconnected extends State {
@@ -331,7 +383,6 @@
             }
             onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_CONNECTING);
 
-            BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
             // When commanded to connect begin SDP to find the MAS server.
             mDevice.sdpSearch(BluetoothUuid.MAS);
             sendMessageDelayed(MSG_CONNECTING_TIMEOUT, TIMEOUT);
@@ -349,9 +400,14 @@
                         Log.d(TAG, "SDP Complete");
                     }
                     if (mMasClient == null) {
-                        mMasClient = new MasClient(mDevice, MceStateMachine.this,
-                                (SdpMasRecord) message.obj);
-                        setDefaultMessageType((SdpMasRecord) message.obj);
+                        SdpMasRecord record = (SdpMasRecord) message.obj;
+                        if (record == null) {
+                            Log.e(TAG, "Unexpected: SDP record is null for device "
+                                    + mDevice.getName());
+                            return NOT_HANDLED;
+                        }
+                        mMasClient = new MasClient(mDevice, MceStateMachine.this, record);
+                        setDefaultMessageType(record);
                     }
                     break;
 
@@ -360,6 +416,9 @@
                     break;
 
                 case MSG_MAS_DISCONNECTED:
+                    if (mMasClient != null) {
+                        mMasClient.shutdown();
+                    }
                     transitionTo(mDisconnected);
                     break;
 
@@ -456,8 +515,12 @@
                             Log.d(TAG, "Message Sent......." + messageHandle);
                         }
                         // ignore the top-order byte (converted to string) in the handle for now
-                        mSentMessageLog.put(messageHandle.substring(2),
-                                ((RequestPushMessage) message.obj).getBMsg());
+                        // some test devices don't populate messageHandle field.
+                        // in such cases, no need to wait up for response for such messages.
+                        if (messageHandle != null && messageHandle.length() > 2) {
+                            mSentMessageLog.put(messageHandle.substring(2),
+                                    ((RequestPushMessage) message.obj).getBMsg());
+                        }
                     } else if (message.obj instanceof RequestGetMessagesListing) {
                         processMessageListing((RequestGetMessagesListing) message.obj);
                     }
@@ -483,6 +546,15 @@
             mPreviousState = BluetoothProfile.STATE_CONNECTED;
         }
 
+        /**
+         * Given a message notification event, will ensure message caching and updating and update
+         * interested applications.
+         *
+         * Message notifications arrive for both remote message reception and Message-Listing object
+         * updates that are triggered by the server side.
+         *
+         * @param msg - A Message object containing a EventReport object describing the remote event
+         */
         private void processNotification(Message msg) {
             if (DBG) {
                 Log.d(TAG, "Handler: msg: " + msg.what);
@@ -491,16 +563,25 @@
             switch (msg.what) {
                 case MSG_NOTIFICATION:
                     EventReport ev = (EventReport) msg.obj;
-                    if (DBG) {
-                        Log.d(TAG, "Message Type = " + ev.getType());
+                    if (ev == null) {
+                        Log.w(TAG, "MSG_NOTIFICATION event is null");
+                        return;
                     }
                     if (DBG) {
-                        Log.d(TAG, "Message handle = " + ev.getHandle());
+                        Log.d(TAG, "Message Type = " + ev.getType()
+                                + ", Message handle = " + ev.getHandle());
                     }
                     switch (ev.getType()) {
 
                         case NEW_MESSAGE:
-                            //mService.get().sendNewMessageNotification(ev);
+                            // Infer the timestamp for this message as 'now' and read status false
+                            // instead of getting the message listing data for it
+                            if (!mMessages.contains(ev.getHandle())) {
+                                Calendar calendar = Calendar.getInstance();
+                                MessageMetadata metadata = new MessageMetadata(ev.getHandle(),
+                                        calendar.getTime().getTime(), false);
+                                mMessages.put(ev.getHandle(), metadata);
+                            }
                             mMasClient.makeRequest(new RequestGetMessage(ev.getHandle(),
                                     MasClient.CharsetType.UTF_8, false));
                             break;
@@ -516,6 +597,8 @@
         // Sets the specified message status to "read" (from "unread" status, mostly)
         private void markMessageRead(RequestGetMessage request) {
             if (DBG) Log.d(TAG, "markMessageRead");
+            MessageMetadata metadata = mMessages.get(request.getHandle());
+            metadata.setRead(true);
             mMasClient.makeRequest(new RequestSetMessageStatus(
                     request.getHandle(), RequestSetMessageStatus.StatusIndicator.READ));
         }
@@ -527,21 +610,44 @@
                     request.getHandle(), RequestSetMessageStatus.StatusIndicator.DELETED));
         }
 
+        /**
+         * Given the result of a Message Listing request, will cache the contents of each Message in
+         * the Message Listing Object and kick off requests to retrieve message contents from the
+         * remote device.
+         *
+         * @param request - A request object that has been resolved and returned with a message list
+         */
         private void processMessageListing(RequestGetMessagesListing request) {
             if (DBG) {
                 Log.d(TAG, "processMessageListing");
             }
-            ArrayList<com.android.bluetooth.mapclient.Message> messageHandles = request.getList();
-            if (messageHandles != null) {
-                for (com.android.bluetooth.mapclient.Message handle : messageHandles) {
+            ArrayList<com.android.bluetooth.mapclient.Message> messageListing = request.getList();
+            if (messageListing != null) {
+                // Message listings by spec arrive ordered newest first but we wish to broadcast as
+                // oldest first. Iterate in reverse order so we initiate requests oldest first.
+                for (int i = messageListing.size() - 1; i >= 0; i--) {
+                    com.android.bluetooth.mapclient.Message msg = messageListing.get(i);
                     if (DBG) {
-                        Log.d(TAG, "getting message ");
+                        Log.d(TAG, "getting message for handle " + msg.getHandle());
                     }
-                    getMessage(handle.getHandle());
+                    // A message listing coming from the server should always have up to date data
+                    mMessages.put(msg.getHandle(), new MessageMetadata(msg.getHandle(),
+                            msg.getDateTime().getTime(), msg.isRead()));
+                    getMessage(msg.getHandle());
                 }
             }
         }
 
+        /**
+         * Given the response of a GetMessage request, will broadcast the bMessage contents on to
+         * all registered applications.
+         *
+         * Inbound messages arrive as bMessage objects following a GetMessage request. GetMessage
+         * uses a message handle that can arrive from both a GetMessageListing request or a Message
+         * Notification event.
+         *
+         * @param request - A request object that has been resolved and returned with message data
+         */
         private void processInboundMessage(RequestGetMessage request) {
             Bmessage message = request.getMessage();
             if (DBG) {
@@ -570,10 +676,18 @@
                         Log.d(TAG, "Recipients" + message.getRecipients().toString());
                     }
 
+                    // Grab the message metadata and update the cached read status from the bMessage
+                    MessageMetadata metadata = mMessages.get(request.getHandle());
+                    metadata.setRead(request.getMessage().getStatus() == Bmessage.Status.READ);
+
                     Intent intent = new Intent();
                     intent.setAction(BluetoothMapClient.ACTION_MESSAGE_RECEIVED);
                     intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
                     intent.putExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE, request.getHandle());
+                    intent.putExtra(BluetoothMapClient.EXTRA_MESSAGE_TIMESTAMP,
+                            metadata.getTimestamp());
+                    intent.putExtra(BluetoothMapClient.EXTRA_MESSAGE_READ_STATUS,
+                            metadata.getRead());
                     intent.putExtra(android.content.Intent.EXTRA_TEXT, message.getBodyContent());
                     VCardEntry originator = message.getOriginator();
                     if (originator != null) {
@@ -592,7 +706,12 @@
                         intent.putExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME,
                                 originator.getDisplayName());
                     }
-                    mService.sendBroadcast(intent);
+                    // Only send to the current default SMS app if one exists
+                    String defaultMessagingPackage = Telephony.Sms.getDefaultSmsPackage(mService);
+                    if (defaultMessagingPackage != null) {
+                        intent.setPackage(defaultMessagingPackage);
+                    }
+                    mService.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
                     break;
 
                 case MMS:
@@ -607,6 +726,9 @@
             if (DBG) {
                 Log.d(TAG, "got a status for " + handle + " Status = " + status);
             }
+            // some test devices don't populate messageHandle field.
+            // in such cases, ignore such messages.
+            if (handle == null || handle.length() <= 2) return;
             PendingIntent intentToSend = null;
             // ignore the top-order byte (converted to string) in the handle for now
             String shortHandle = handle.substring(2);
@@ -688,10 +810,8 @@
 
     void receiveEvent(EventReport ev) {
         if (DBG) {
-            Log.d(TAG, "Message Type = " + ev.getType());
-        }
-        if (DBG) {
-            Log.d(TAG, "Message handle = " + ev.getHandle());
+            Log.d(TAG, "Message Type = " + ev.getType()
+                    + ", Message handle = " + ev.getHandle());
         }
         sendMessage(MSG_NOTIFICATION, ev);
     }
diff --git a/src/com/android/bluetooth/mapclient/obex/ObexTime.java b/src/com/android/bluetooth/mapclient/obex/ObexTime.java
index 42a32c1..cc58a51 100644
--- a/src/com/android/bluetooth/mapclient/obex/ObexTime.java
+++ b/src/com/android/bluetooth/mapclient/obex/ObexTime.java
@@ -29,8 +29,17 @@
 
     public ObexTime(String time) {
         /*
-         * match OBEX time string: YYYYMMDDTHHMMSS with optional UTF offset
-         * +/-hhmm
+         * Match OBEX time string: YYYYMMDDTHHMMSS with optional UTF offset +/-hhmm
+         *
+         * Matched groups are numberes as follows:
+         *
+         *     YYYY MM DD T HH MM SS + hh mm
+         *     ^^^^ ^^ ^^   ^^ ^^ ^^ ^ ^^ ^^
+         *     1    2  3    4  5  6  8 9  10
+         *                          |---7---|
+         *
+         * All groups are guaranteed to be numeric so conversion will always succeed (except group 8
+         * which is either + or -)
          */
         Pattern p = Pattern.compile(
                 "(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})(([+-])(\\d{2})(\\d{2})" + ")?");
@@ -39,20 +48,26 @@
         if (m.matches()) {
 
             /*
-             * matched groups are numberes as follows: YYYY MM DD T HH MM SS +
-             * hh mm ^^^^ ^^ ^^ ^^ ^^ ^^ ^ ^^ ^^ 1 2 3 4 5 6 8 9 10 all groups
-             * are guaranteed to be numeric so conversion will always succeed
-             * (except group 8 which is either + or -)
+             * MAP spec says to default to "Local Time basis" for a message listing timestamp. We'll
+             * use the system default timezone and assume it knows best what our local timezone is.
+             * The builder defaults to the default locale and timezone if none is provided.
              */
+            Calendar.Builder builder = new Calendar.Builder();
 
-            Calendar cal = Calendar.getInstance();
-            cal.set(Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)) - 1,
-                    Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)),
-                    Integer.parseInt(m.group(5)), Integer.parseInt(m.group(6)));
+            /* Note that Calendar months are zero-based */
+            builder.setDate(Integer.parseInt(m.group(1)), /* year */
+                    Integer.parseInt(m.group(2)) - 1,     /* month */
+                    Integer.parseInt(m.group(3)));        /* day of month */
+
+            /* Note the MAP timestamp doesn't have milliseconds and we're explicitly setting to 0 */
+            builder.setTimeOfDay(Integer.parseInt(m.group(4)), /* hours */
+                    Integer.parseInt(m.group(5)),              /* minutes */
+                    Integer.parseInt(m.group(6)),              /* seconds */
+                    0);                                        /* milliseconds */
 
             /*
-             * if 7th group is matched then we have UTC offset information
-             * included
+             * If 7th group is matched then we're no longer using "Local Time basis" and instead
+             * have a UTC based timestamp and offset information included
              */
             if (m.group(7) != null) {
                 int ohh = Integer.parseInt(m.group(9));
@@ -68,10 +83,10 @@
                 TimeZone tz = TimeZone.getTimeZone("UTC");
                 tz.setRawOffset(offset);
 
-                cal.setTimeZone(tz);
+                builder.setTimeZone(tz);
             }
 
-            mDate = cal.getTime();
+            mDate = builder.build().getTime();
         }
     }
 
diff --git a/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java b/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
index 166983a..53b785f 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
@@ -23,9 +23,10 @@
 import android.net.Uri;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.support.v4.content.FileProvider;
 import android.util.Log;
 
+import androidx.core.content.FileProvider;
+
 import java.io.File;
 
 /**
diff --git a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
index 6e83480..bae8cb9 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/BluetoothOppLauncherActivity.java b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
index 99e3e69..1a15adb 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
@@ -248,7 +253,7 @@
                 Settings.System.getString(resolver, Settings.Global.AIRPLANE_MODE_RADIOS);
         final boolean isAirplaneSensitive =
                 airplaneModeRadios == null || airplaneModeRadios.contains(
-                        Settings.System.RADIO_BLUETOOTH);
+                        Settings.Global.RADIO_BLUETOOTH);
         if (!isAirplaneSensitive) {
             return true;
         }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppNotification.java b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
index 41ffec3..1e92996 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppNotification.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
@@ -40,6 +40,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Message;
@@ -557,12 +558,14 @@
                     .setClassName(Constants.THIS_PACKAGE_NAME,
                             BluetoothOppReceiver.class.getName());
             Notification.Action actionDecline =
-                    new Notification.Action.Builder(R.drawable.ic_decline,
+                    new Notification.Action.Builder(Icon.createWithResource(mContext,
+                            R.drawable.ic_decline),
                             mContext.getText(R.string.incoming_file_confirm_cancel),
                             PendingIntent.getBroadcast(mContext, 0,
                                     new Intent(baseIntent).setAction(Constants.ACTION_DECLINE),
                                     0)).build();
-            Notification.Action actionAccept = new Notification.Action.Builder(R.drawable.ic_accept,
+            Notification.Action actionAccept = new Notification.Action.Builder(
+                    Icon.createWithResource(mContext,R.drawable.ic_accept),
                     mContext.getText(R.string.incoming_file_confirm_ok),
                     PendingIntent.getBroadcast(mContext, 0,
                             new Intent(baseIntent).setAction(Constants.ACTION_ACCEPT), 0)).build();
@@ -589,7 +592,7 @@
                             .setStyle(new Notification.BigTextStyle().bigText(mContext.getString(
                                     R.string.incoming_file_confirm_Notification_content,
                                     info.mDeviceName, info.mFileName)))
-                            .setContentInfo(Formatter.formatFileSize(mContext, info.mTotalBytes))
+                            .setSubText(Formatter.formatFileSize(mContext, info.mTotalBytes))
                             .setSmallIcon(R.drawable.bt_incomming_file_notification)
                             .setLocalOnly(true)
                             .build();
diff --git a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
index 73d0194..f072d51 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
@@ -58,7 +58,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(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) {
             BluetoothOppManager mOppManager = BluetoothOppManager.getInstance(context);
 
diff --git a/src/com/android/bluetooth/opp/BluetoothOppService.java b/src/com/android/bluetooth/opp/BluetoothOppService.java
index 383a497..914b9b6 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -52,7 +52,6 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.Process;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothObexTransport;
@@ -60,6 +59,7 @@
 import com.android.bluetooth.ObexServerSockets;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.sdp.SdpManager;
+import com.android.internal.annotations.VisibleForTesting;
 
 import com.google.android.collect.Lists;
 
@@ -115,6 +115,8 @@
 
     private UpdateThread mUpdateThread;
 
+    private boolean mUpdateThreadRunning;
+
     private ArrayList<BluetoothOppShareInfo> mShares;
 
     private ArrayList<BluetoothOppBatch> mBatches;
@@ -241,6 +243,10 @@
 
     @Override
     public boolean stop() {
+        if (sBluetoothOppService == null) {
+            Log.w(TAG, "stop() called before start()");
+            return true;
+        }
         setBluetoothOppService(null);
         mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
         return true;
@@ -330,8 +336,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,6 +356,7 @@
                             mUpdateThread = null;
                         }
                     }
+
                     mNotifier.cancelNotifications();
                     break;
                 case START_LISTENER:
@@ -551,6 +569,7 @@
             if (mUpdateThread == null) {
                 mUpdateThread = new UpdateThread();
                 mUpdateThread.start();
+                mUpdateThreadRunning = true;
             }
         }
     }
@@ -580,6 +599,7 @@
             while (!mIsInterrupted) {
                 synchronized (BluetoothOppService.this) {
                     if (mUpdateThread != this) {
+                        mUpdateThreadRunning = false;
                         throw new IllegalStateException(
                                 "multiple UpdateThreads in BluetoothOppService");
                     }
@@ -589,6 +609,7 @@
                     }
                     if (!mPendingUpdate) {
                         mUpdateThread = null;
+                        mUpdateThreadRunning = false;
                         return;
                     }
                     mPendingUpdate = false;
@@ -598,6 +619,7 @@
                                 BluetoothShare._ID);
 
                 if (cursor == null) {
+                    mUpdateThreadRunning = false;
                     return;
                 }
 
@@ -634,9 +656,6 @@
                             }
                         }
 
-                        if (shouldScanFile(arrayPos)) {
-                            scanFile(arrayPos);
-                        }
                         deleteShare(arrayPos); // this advances in the array
                     } else {
                         int id = cursor.getInt(idColumn);
@@ -660,14 +679,11 @@
                                     Log.v(TAG,
                                             "Array update: removing " + arrayId + " @ " + arrayPos);
                                 }
-                                if (shouldScanFile(arrayPos)) {
-                                    scanFile(arrayPos);
-                                }
                                 deleteShare(arrayPos);
                             } else if (arrayId == id) {
                                 // This cursor row already exists in the stored array.
                                 updateShare(cursor, arrayPos);
-
+                                scanFileIfNeeded(arrayPos);
                                 ++arrayPos;
                                 cursor.moveToNext();
                                 isAfterLast = cursor.isAfterLast();
@@ -691,6 +707,8 @@
 
                 cursor.close();
             }
+
+            mUpdateThreadRunning = false;
         }
     }
 
@@ -1031,8 +1049,14 @@
         }
     }
 
-    private boolean scanFile(int arrayPos) {
+    private void scanFileIfNeeded(int arrayPos) {
         BluetoothOppShareInfo info = mShares.get(arrayPos);
+        boolean isFileReceived = BluetoothShare.isStatusSuccess(info.mStatus)
+                && info.mDirection == BluetoothShare.DIRECTION_INBOUND && !info.mMediaScanned
+                && info.mConfirm != BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED;
+        if (!isFileReceived) {
+            return;
+        }
         synchronized (BluetoothOppService.this) {
             if (D) {
                 Log.d(TAG, "Scanning file " + info.mFilename);
@@ -1040,20 +1064,10 @@
             if (!mMediaScanInProgress) {
                 mMediaScanInProgress = true;
                 new MediaScannerNotifier(this, info, mHandler);
-                return true;
-            } else {
-                return false;
             }
         }
     }
 
-    private boolean shouldScanFile(int arrayPos) {
-        BluetoothOppShareInfo info = mShares.get(arrayPos);
-        return BluetoothShare.isStatusSuccess(info.mStatus)
-                && info.mDirection == BluetoothShare.DIRECTION_INBOUND && !info.mMediaScanned
-                && info.mConfirm != BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED;
-    }
-
     // Run in a background thread at boot.
     private static void trimDatabase(ContentResolver contentResolver) {
         // remove the invisible/unconfirmed inbound shares
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java b/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
index 74fd872..fc45d3f 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
@@ -309,7 +309,10 @@
         } else if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_FAIL) {
             if (mTransInfo.mStatus == BluetoothShare.STATUS_ERROR_SDCARD_FULL) {
                 mLine1View = (TextView) mView.findViewById(R.id.line1_view);
-                tmp = getString(R.string.bt_sm_2_1, mTransInfo.mDeviceName);
+                int id = BluetoothOppUtility.deviceHasNoSdCard()
+                        ? R.string.bt_sm_2_1_nosdcard
+                        : R.string.bt_sm_2_1_default;
+                tmp = getString(id);
                 mLine1View.setText(tmp);
                 mLine2View = (TextView) mView.findViewById(R.id.line2_view);
                 tmp = getString(R.string.download_fail_line2, mTransInfo.mFileName);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java b/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java
index 7221c53..134b2d6 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java
@@ -58,7 +58,7 @@
     private Context mContext;
 
     public BluetoothOppTransferAdapter(Context context, int layout, Cursor c) {
-        super(context, layout, c);
+        super(context, layout, c, true /* autoRequery */ );
         mContext = context;
     }
 
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index 1b5cd59..7533d0d 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -45,6 +45,7 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Environment;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.bluetooth.R;
@@ -56,6 +57,7 @@
 import java.math.RoundingMode;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -66,6 +68,8 @@
     private static final String TAG = "BluetoothOppUtility";
     private static final boolean D = Constants.DEBUG;
     private static final boolean V = Constants.VERBOSE;
+    /** Whether the device has the "nosdcard" characteristic, or null if not-yet-known. */
+    private static Boolean sNoSdCard = null;
 
     private static final ConcurrentHashMap<Uri, BluetoothOppSendFileInfo> sSendFileMap =
             new ConcurrentHashMap<Uri, BluetoothOppSendFileInfo>();
@@ -292,6 +296,17 @@
     }
 
     /**
+     * Whether the device has the "nosdcard" characteristic or not.
+     */
+    public static boolean deviceHasNoSdCard() {
+        if (sNoSdCard == null) {
+            String characteristics = SystemProperties.get("ro.build.characteristics", "");
+            sNoSdCard = Arrays.asList(characteristics).contains("nosdcard");
+        }
+        return sNoSdCard;
+    }
+
+    /**
      * Get status description according to status code.
      */
     public static String getStatusDescription(Context context, int statusCode, String deviceName) {
@@ -311,11 +326,15 @@
         } else if (statusCode == BluetoothShare.STATUS_FILE_ERROR) {
             ret = context.getString(R.string.status_file_error);
         } else if (statusCode == BluetoothShare.STATUS_ERROR_NO_SDCARD) {
-            ret = context.getString(R.string.status_no_sd_card);
+            int id = deviceHasNoSdCard()
+                    ? R.string.status_no_sd_card_nosdcard
+                    : R.string.status_no_sd_card_default;
+            ret = context.getString(id);
         } else if (statusCode == BluetoothShare.STATUS_CONNECTION_ERROR) {
             ret = context.getString(R.string.status_connection_error);
         } else if (statusCode == BluetoothShare.STATUS_ERROR_SDCARD_FULL) {
-            ret = context.getString(R.string.bt_sm_2_1, deviceName);
+            int id = deviceHasNoSdCard() ? R.string.bt_sm_2_1_nosdcard : R.string.bt_sm_2_1_default;
+            ret = context.getString(id);
         } else if ((statusCode == BluetoothShare.STATUS_BAD_REQUEST) || (statusCode
                 == BluetoothShare.STATUS_LENGTH_REQUIRED) || (statusCode
                 == BluetoothShare.STATUS_PRECONDITION_FAILED) || (statusCode
diff --git a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
index f477dd9..72016f7 100644
--- a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
+++ b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
@@ -25,12 +25,17 @@
 import android.net.NetworkFactory;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
-import android.net.ip.IpClient;
-import android.net.ip.IpClient.WaitForProvisioningCallback;
+import android.net.ip.IIpClient;
+import android.net.ip.IpClientUtil;
+import android.net.ip.IpClientUtil.WaitForProvisioningCallbacks;
+import android.net.shared.ProvisioningConfiguration;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
+
 /**
  * This class tracks the data connection associated with Bluetooth
  * reverse tethering. PanService calls it when a reverse tethered
@@ -49,7 +54,9 @@
 
     // All accesses to these must be synchronized(this).
     private final NetworkInfo mNetworkInfo;
-    private IpClient mIpClient;
+    private IIpClient mIpClient;
+    @GuardedBy("this")
+    private int mIpClientStartIndex = 0;
     private String mInterfaceName;
     private NetworkAgent mNetworkAgent;
 
@@ -65,13 +72,64 @@
         setCapabilityFilter(mNetworkCapabilities);
     }
 
+    private class BtIpClientCallback extends WaitForProvisioningCallbacks {
+        private final int mCurrentStartIndex;
+
+        private BtIpClientCallback(int currentStartIndex) {
+            mCurrentStartIndex = currentStartIndex;
+        }
+
+        @Override
+        public void onIpClientCreated(IIpClient ipClient) {
+            synchronized (BluetoothTetheringNetworkFactory.this) {
+                if (mCurrentStartIndex != mIpClientStartIndex) {
+                    // Do not start IpClient: the current request is obsolete.
+                    // IpClient will be GCed eventually as the IIpClient Binder token goes out
+                    // of scope.
+                    return;
+                }
+                mIpClient = ipClient;
+                try {
+                    mIpClient.startProvisioning(new ProvisioningConfiguration.Builder()
+                            .withoutMultinetworkPolicyTracker()
+                            .withoutIpReachabilityMonitor()
+                            .build().toStableParcelable());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error starting IpClient provisioning", e);
+                }
+            }
+        }
+
+        @Override
+        public void onLinkPropertiesChange(LinkProperties newLp) {
+            synchronized (BluetoothTetheringNetworkFactory.this) {
+                if (mNetworkAgent != null && mNetworkInfo.isConnected()) {
+                    mNetworkAgent.sendLinkProperties(newLp);
+                }
+            }
+        }
+    }
+
     private void stopIpClientLocked() {
+        // Mark all previous start requests as obsolete
+        mIpClientStartIndex++;
         if (mIpClient != null) {
-            mIpClient.shutdown();
+            try {
+                mIpClient.shutdown();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error shutting down IpClient", e);
+            }
             mIpClient = null;
         }
     }
 
+    private BtIpClientCallback startIpClientLocked() {
+        mIpClientStartIndex++;
+        final BtIpClientCallback callback = new BtIpClientCallback(mIpClientStartIndex);
+        IpClientUtil.makeIpClient(mContext, mInterfaceName, callback);
+        return callback;
+    }
+
     // Called by NetworkFactory when PanService and NetworkFactory both desire a Bluetooth
     // reverse-tether connection.  A network interface for Bluetooth reverse-tethering can be
     // assumed to be available because we only register our NetworkFactory when it is so.
@@ -84,16 +142,7 @@
             @Override
             public void run() {
                 LinkProperties linkProperties;
-                final WaitForProvisioningCallback ipcCallback = new WaitForProvisioningCallback() {
-                    @Override
-                    public void onLinkPropertiesChange(LinkProperties newLp) {
-                        synchronized (BluetoothTetheringNetworkFactory.this) {
-                            if (mNetworkAgent != null && mNetworkInfo.isConnected()) {
-                                mNetworkAgent.sendLinkProperties(newLp);
-                            }
-                        }
-                    }
-                };
+                final WaitForProvisioningCallbacks ipcCallback;
 
                 synchronized (BluetoothTetheringNetworkFactory.this) {
                     if (TextUtils.isEmpty(mInterfaceName)) {
@@ -102,11 +151,7 @@
                     }
                     log("ipProvisioningThread(+" + mInterfaceName + "): " + "mNetworkInfo="
                             + mNetworkInfo);
-                    mIpClient = new IpClient(mContext, mInterfaceName, ipcCallback);
-                    mIpClient.startProvisioning(mIpClient.buildProvisioningConfiguration()
-                            .withoutMultinetworkPolicyTracker()
-                            .withoutIpReachabilityMonitor()
-                            .build());
+                    ipcCallback = startIpClientLocked();
                     mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, null);
                 }
 
diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java
index 700b395..8c52df7 100644
--- a/src/com/android/bluetooth/pan/PanService.java
+++ b/src/com/android/bluetooth/pan/PanService.java
@@ -34,11 +34,11 @@
 import android.os.Message;
 import android.os.ServiceManager;
 import android.os.UserManager;
-import android.provider.Settings;
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
 
@@ -398,11 +398,11 @@
             throw new IllegalArgumentException("Null device");
         }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothPanPriorityKey(device.getAddress()), priority);
         if (DBG) {
             Log.d(TAG, "Saved priority " + device + " = " + priority);
         }
+        AdapterService.getAdapterService().getDatabase()
+                .setProfilePriority(device, BluetoothProfile.PAN, priority);
         return true;
     }
 
@@ -411,9 +411,8 @@
             throw new IllegalArgumentException("Null device");
         }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        return Settings.Global.getInt(getContentResolver(),
-                Settings.Global.getBluetoothPanPriorityKey(device.getAddress()),
-                BluetoothProfile.PRIORITY_UNDEFINED);
+        return AdapterService.getAdapterService().getDatabase()
+                .getProfilePriority(device, BluetoothProfile.PAN);
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
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/BluetoothPbapCallLogComposer.java b/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java
index 805f3ea..b5fb51b 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java
@@ -23,7 +23,6 @@
 import android.provider.CallLog;
 import android.provider.CallLog.Calls;
 import android.text.TextUtils;
-import android.text.format.Time;
 import android.util.Log;
 
 import com.android.bluetooth.R;
@@ -32,13 +31,15 @@
 import com.android.vcard.VCardConstants;
 import com.android.vcard.VCardUtils;
 
+import java.text.SimpleDateFormat;
 import java.util.Arrays;
+import java.util.Calendar;
 
 /**
  * VCard composer especially for Call Log used in Bluetooth.
  */
 public class BluetoothPbapCallLogComposer {
-    private static final String TAG = "CallLogComposer";
+    private static final String TAG = "PbapCallLogComposer";
 
     private static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
             "Failed to get database information";
@@ -86,6 +87,8 @@
 
     private String mErrorReason = NO_ERROR;
 
+    private final String RFC_2455_FORMAT = "yyyyMMdd'T'HHmmss";
+
     public BluetoothPbapCallLogComposer(final Context context) {
         mContext = context;
         mContentResolver = context.getContentResolver();
@@ -198,9 +201,10 @@
      * The format is: ("%Y%m%dT%H%M%S").
      */
     private String toRfc2455Format(final long millSecs) {
-        Time startDate = new Time();
-        startDate.set(millSecs);
-        return startDate.format2445();
+        Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(millSecs);
+        SimpleDateFormat df = new SimpleDateFormat(RFC_2455_FORMAT);
+        return df.format(cal.getTime());
     }
 
     /**
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
old mode 100644
new mode 100755
index 247a76d..306f31d
--- a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
@@ -817,50 +817,65 @@
         }
 
         if (type.equals("number")) {
+            ArrayList<Integer> savedPosList = new ArrayList<>();
+            ArrayList<String> selectedNameList = new ArrayList<String>();
             // query the number, to get the names
             ArrayList<String> names =
                     mVcardManager.getContactNamesByNumber(appParamValue.searchValue);
             if (mOrderBy == ORDER_BY_ALPHABETICAL) Collections.sort(names);
             for (int i = 0; i < names.size(); i++) {
                 compareValue = names.get(i).trim();
-                if (D) {
-                    Log.d(TAG, "compareValue=" + compareValue);
-                }
-                for (int pos = appParamValue.listStartOffset;
-                        pos < listSize && itemsFound < requestSize; pos++) {
+                if (D) Log.d(TAG, "compareValue=" + compareValue);
+                for (int pos = 0; pos < listSize; pos++) {
                     currentValue = nameList.get(pos);
                     if (V) {
                         Log.d(TAG, "currentValue=" + currentValue);
                     }
                     if (currentValue.equals(compareValue)) {
-                        itemsFound++;
                         if (currentValue.contains(",")) {
                             currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
                         }
-                        writeVCardEntry(pos, currentValue, result);
+                        selectedNameList.add(currentValue);
+                        savedPosList.add(pos);
                     }
                 }
-                if (itemsFound >= requestSize) {
-                    break;
-                }
             }
+
+            for (int j = appParamValue.listStartOffset;
+                    j < selectedNameList.size() && itemsFound < requestSize; j++) {
+                itemsFound++;
+                writeVCardEntry(savedPosList.get(j), selectedNameList.get(j), result);
+            }
+
         } else {
+            ArrayList<Integer> savedPosList = new ArrayList<>();
+            ArrayList<String> selectedNameList = new ArrayList<String>();
             if (appParamValue.searchValue != null) {
                 compareValue = appParamValue.searchValue.trim().toLowerCase();
             }
-            for (int pos = appParamValue.listStartOffset;
-                    pos < listSize && itemsFound < requestSize; pos++) {
+
+            for (int pos = 0; pos < listSize; pos++) {
                 currentValue = nameList.get(pos);
+
                 if (currentValue.contains(",")) {
                     currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
                 }
 
-                if (appParamValue.searchValue.isEmpty() || ((currentValue.toLowerCase()).startsWith(
-                        compareValue))) {
-                    itemsFound++;
-                    writeVCardEntry(pos, currentValue, result);
+                if (appParamValue.searchValue != null) {
+                    if (appParamValue.searchValue.isEmpty()
+                            || ((currentValue.toLowerCase())
+                                               .startsWith(compareValue.toLowerCase()))) {
+                        selectedNameList.add(currentValue);
+                        savedPosList.add(pos);
+                    }
                 }
             }
+
+            for (int i = appParamValue.listStartOffset;
+                    i < selectedNameList.size() && itemsFound < requestSize; i++) {
+                itemsFound++;
+                writeVCardEntry(savedPosList.get(i), selectedNameList.get(i), result);
+            }
         }
         return itemsFound;
     }
@@ -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);
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
index 374b5a1..e7dba2a 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -49,7 +49,6 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.UserManager;
-import android.support.annotation.VisibleForTesting;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
@@ -61,6 +60,7 @@
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.sdp.SdpManager;
 import com.android.bluetooth.util.DevicePolicyUtils;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -127,6 +127,7 @@
     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 USER_CONFIRM_TIMEOUT_VALUE = 30000;
     static final int RELEASE_WAKE_LOCK_DELAY = 10000;
@@ -408,6 +409,8 @@
                         mPbapStateMachineMap.remove(remoteDevice);
                     }
                     break;
+                case GET_LOCAL_TELEPHONY_DETAILS:
+                    getLocalTelephonyDetails();
                 default:
                     break;
             }
@@ -507,18 +510,12 @@
             Log.e(TAG, "Illegal state exception, content observer is already registered");
         }
 
-        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;
     }
 
@@ -686,7 +683,7 @@
      * Send the result to the state machine.
      * @param stateMachine PbapStateMachine which sends the request
      */
-    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void checkOrGetPhonebookPermission(PbapStateMachine stateMachine) {
         BluetoothDevice device = stateMachine.getRemoteDevice();
         int permission = device.getPhonebookAccessPermission();
@@ -775,4 +772,18 @@
             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/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
old mode 100644
new mode 100755
index e58fa2e..5ba2b4b
--- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
@@ -332,12 +332,18 @@
             contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null,
                     Phone.CONTACT_ID);
 
+            ArrayList<String> contactNameIdList = new ArrayList<String>();
+            appendDistinctNameIdList(contactNameIdList,
+                    mContext.getString(android.R.string.unknownName), contactCursor);
+
             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 +368,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) {
diff --git a/src/com/android/bluetooth/pbapclient/Authenticator.java b/src/com/android/bluetooth/pbapclient/Authenticator.java
index 0811499..3a096f1 100644
--- a/src/com/android/bluetooth/pbapclient/Authenticator.java
+++ b/src/com/android/bluetooth/pbapclient/Authenticator.java
@@ -26,6 +26,7 @@
 
 public class Authenticator extends AbstractAccountAuthenticator {
     private static final String TAG = "PbapAuthenticator";
+    private static final boolean DBG = Utils.DBG;
 
     public Authenticator(Context context) {
         super(context);
@@ -34,7 +35,7 @@
     // Editing properties is not supported
     @Override
     public Bundle editProperties(AccountAuthenticatorResponse r, String s) {
-        Log.d(TAG, "got call", new Exception());
+        if (DBG) Log.d(TAG, "got call", new Exception());
         throw new UnsupportedOperationException();
     }
 
@@ -42,7 +43,7 @@
     @Override
     public Bundle addAccount(AccountAuthenticatorResponse r, String s, String s2, String[] strings,
             Bundle bundle) throws NetworkErrorException {
-        Log.d(TAG, "got call", new Exception());
+        if (DBG) Log.d(TAG, "got call", new Exception());
         // Don't allow accounts to be added.
         throw new UnsupportedOperationException();
     }
@@ -51,7 +52,7 @@
     @Override
     public Bundle confirmCredentials(AccountAuthenticatorResponse r, Account account, Bundle bundle)
             throws NetworkErrorException {
-        Log.d(TAG, "got call", new Exception());
+        if (DBG) Log.d(TAG, "got call", new Exception());
         return null;
     }
 
@@ -59,14 +60,14 @@
     @Override
     public Bundle getAuthToken(AccountAuthenticatorResponse r, Account account, String s,
             Bundle bundle) throws NetworkErrorException {
-        Log.d(TAG, "got call", new Exception());
+        if (DBG) Log.d(TAG, "got call", new Exception());
         throw new UnsupportedOperationException();
     }
 
     // Getting a label for the auth token is not supported
     @Override
     public String getAuthTokenLabel(String s) {
-        Log.d(TAG, "got call", new Exception());
+        if (DBG) Log.d(TAG, "got call", new Exception());
         return null;
     }
 
@@ -74,7 +75,7 @@
     @Override
     public Bundle updateCredentials(AccountAuthenticatorResponse r, Account account, String s,
             Bundle bundle) throws NetworkErrorException {
-        Log.d(TAG, "got call", new Exception());
+        if (DBG) Log.d(TAG, "got call", new Exception());
         return null;
     }
 
@@ -82,7 +83,7 @@
     @Override
     public Bundle hasFeatures(AccountAuthenticatorResponse r, Account account, String[] strings)
             throws NetworkErrorException {
-        Log.d(TAG, "got call", new Exception());
+        if (DBG) Log.d(TAG, "got call", new Exception());
 
         final Bundle result = new Bundle();
         result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
index ab8091a..f14a805 100644
--- a/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
@@ -30,7 +30,8 @@
 
 class BluetoothPbapObexAuthenticator implements Authenticator {
 
-    private static final String TAG = "BluetoothPbapObexAuthenticator";
+    private static final String TAG = "BtPbapObexAuthenticator";
+    private static final boolean DBG = Utils.DBG;
 
     //Default session key for legacy devices is 0000
     private String mSessionKey = "0000";
@@ -45,13 +46,16 @@
     public PasswordAuthentication onAuthenticationChallenge(String description,
             boolean isUserIdRequired, boolean isFullAccess) {
         PasswordAuthentication pa = null;
-        Log.v(TAG, "onAuthenticationChallenge: starting");
+        if (DBG) Log.v(TAG, "onAuthenticationChallenge: starting");
 
         if (mSessionKey != null && mSessionKey.length() != 0) {
-            Log.v(TAG, "onAuthenticationChallenge: mSessionKey=" + mSessionKey);
+            if (DBG) Log.v(TAG, "onAuthenticationChallenge: mSessionKey=" + mSessionKey);
             pa = new PasswordAuthentication(null, mSessionKey.getBytes());
         } else {
-            Log.v(TAG, "onAuthenticationChallenge: mSessionKey is empty, timeout/cancel occured");
+            if (DBG) {
+                Log.v(TAG,
+                        "onAuthenticationChallenge: mSessionKey is empty, timeout/cancel occured");
+            }
         }
 
         return pa;
@@ -59,7 +63,7 @@
 
     @Override
     public byte[] onAuthenticationResponse(byte[] userName) {
-        Log.v(TAG, "onAuthenticationResponse: " + userName);
+        if (DBG) Log.v(TAG, "onAuthenticationResponse: " + userName);
         /* required only in case PCE challenges PSE which we don't do now */
         return null;
     }
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java
index 1bd71ce..8b8db79 100644
--- a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java
@@ -29,6 +29,7 @@
 abstract class BluetoothPbapRequest {
 
     private static final String TAG = "BluetoothPbapRequest";
+    private static final boolean DBG = Utils.DBG;
 
     protected static final byte OAP_TAGID_ORDER = 0x01;
     protected static final byte OAP_TAGID_SEARCH_VALUE = 0x02;
@@ -58,7 +59,7 @@
     }
 
     public void execute(ClientSession session) throws IOException {
-        Log.v(TAG, "execute");
+        if (DBG) Log.v(TAG, "execute");
 
         /* in case request is aborted before can be executed */
         if (mAborted) {
@@ -88,7 +89,7 @@
 
             mResponseCode = mOp.getResponseCode();
 
-            Log.d(TAG, "mResponseCode=" + mResponseCode);
+            if (DBG) Log.d(TAG, "mResponseCode=" + mResponseCode);
 
             checkResponseCode(mResponseCode);
         } catch (IOException e) {
@@ -112,19 +113,19 @@
     }
 
     protected void readResponse(InputStream stream) throws IOException {
-        Log.v(TAG, "readResponse");
+        if (DBG) Log.v(TAG, "readResponse");
 
         /* nothing here by default */
     }
 
     protected void readResponseHeaders(HeaderSet headerset) {
-        Log.v(TAG, "readResponseHeaders");
+        if (DBG) Log.v(TAG, "readResponseHeaders");
 
         /* nothing here by dafault */
     }
 
     protected void checkResponseCode(int responseCode) throws IOException {
-        Log.v(TAG, "checkResponseCode");
+        if (DBG) Log.v(TAG, "checkResponseCode");
 
         /* nothing here by dafault */
     }
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
index ddbf215..a5b3fbf 100644
--- a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
@@ -29,9 +29,9 @@
 
 final class BluetoothPbapRequestPullPhoneBook extends BluetoothPbapRequest {
 
-    private static final boolean VDBG = false;
+    private static final boolean VDBG = Utils.VDBG;
 
-    private static final String TAG = "BluetoothPbapRequestPullPhoneBook";
+    private static final String TAG = "BtPbapReqPullPhoneBook";
 
     private static final String TYPE = "x-bt/phonebook";
 
@@ -73,7 +73,7 @@
         oap.add(OAP_TAGID_FORMAT, format);
 
         /*
-         * maxListCount is a special case which is handled in
+         * maxListCount == 0 is a special case which is handled in
          * BluetoothPbapRequestPullPhoneBookSize
          */
         if (maxListCount > 0) {
@@ -93,7 +93,7 @@
 
     @Override
     protected void readResponse(InputStream stream) throws IOException {
-        Log.v(TAG, "readResponse");
+        if (VDBG) Log.v(TAG, "readResponse");
 
         mResponse = new BluetoothPbapVcardList(mAccount, stream, mFormat);
         if (VDBG) {
@@ -103,7 +103,7 @@
 
     @Override
     protected void readResponseHeaders(HeaderSet headerset) {
-        Log.v(TAG, "readResponseHeaders");
+        if (VDBG) Log.v(TAG, "readResponseHeaders");
 
         ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
 
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java
new file mode 100644
index 0000000..f070661
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import javax.obex.HeaderSet;
+
+final class BluetoothPbapRequestPullPhoneBookSize extends BluetoothPbapRequest {
+
+    private static final boolean VDBG = Utils.VDBG;
+
+    private static final String TAG = "BtPbapReqPullPhoneBookSize";
+
+    private static final String TYPE = "x-bt/phonebook";
+
+    private int mSize;
+
+    BluetoothPbapRequestPullPhoneBookSize(String pbName, long filter) {
+        mHeaderSet.setHeader(HeaderSet.NAME, pbName);
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+        // Set MaxListCount in the request to 0 to get PhonebookSize in the response.
+        // If a vCardSelector is present in the request, then the result shall
+        // contain the number of items that satisfy the selector’s criteria.
+        // See PBAP v1.2.3, Sec. 5.1.4.5.
+        oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) 0);
+        if (filter != 0) {
+            oap.add(OAP_TAGID_FILTER, filter);
+        }
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    protected void readResponseHeaders(HeaderSet headerset) {
+        if (VDBG) {
+            Log.v(TAG, "readResponseHeaders");
+        }
+
+        ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+        if (oap.exists(OAP_TAGID_PHONEBOOK_SIZE)) {
+            mSize = oap.getShort(OAP_TAGID_PHONEBOOK_SIZE);
+        }
+    }
+
+    public int getSize() {
+        return mSize;
+    }
+}
diff --git a/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java b/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
index 77665a5..8ab9a1a 100644
--- a/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
+++ b/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
@@ -39,8 +39,8 @@
 import java.util.List;
 
 public class CallLogPullRequest extends PullRequest {
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
+    private static final boolean DBG = Utils.DBG;
+    private static final boolean VDBG = Utils.VDBG;
     private static final String TAG = "PbapCallLogPullRequest";
     private static final String TIMESTAMP_PROPERTY = "X-IRMC-CALL-DATETIME";
     private static final String TIMESTAMP_FORMAT = "yyyyMMdd'T'HHmmss";
@@ -88,9 +88,10 @@
                 ContentValues values = new ContentValues();
 
                 values.put(CallLog.Calls.TYPE, type);
-                values.put(Calls.PHONE_ACCOUNT_ID, mAccount.hashCode());
+                values.put(Calls.PHONE_ACCOUNT_ID, mAccount.name);
                 List<PhoneData> phones = vcard.getPhoneList();
-                if (phones == null || phones.get(0).getNumber().equals(";")) {
+                if (phones == null || phones.get(0).getNumber().equals(";")
+                        || phones.get(0).getNumber().length() == 0) {
                     values.put(CallLog.Calls.NUMBER, "");
                 } else {
                     String phoneNumber = phones.get(0).getNumber();
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
index 913ba0e..f29f0f8 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
@@ -46,8 +46,18 @@
  * controlling state machine.
  */
 class PbapClientConnectionHandler extends Handler {
-    static final String TAG = "PBAP PCE handler";
-    static final boolean DBG = true;
+    // Tradeoff: larger BATCH_SIZE leads to faster download rates, while smaller
+    // BATCH_SIZE is less prone to IO Exceptions if there is a download in
+    // progress when Bluetooth stack is torn down.
+    private static final int DEFAULT_BATCH_SIZE = 250;
+
+    // Upper limit on the indices of the vcf cards/entries, inclusive,
+    // i.e., valid indices are [0, 1, ... , UPPER_LIMIT]
+    private static final int UPPER_LIMIT = 65535;
+
+    static final String TAG = "PbapClientConnHandler";
+    static final boolean DBG = Utils.DBG;
+    static final boolean VDBG = Utils.VDBG;
     static final int MSG_CONNECT = 1;
     static final int MSG_DISCONNECT = 2;
     static final int MSG_DOWNLOAD = 3;
@@ -91,7 +101,6 @@
     private static final long PBAP_REQUESTED_FIELDS =
             PBAP_FILTER_VERSION | PBAP_FILTER_FN | PBAP_FILTER_N | PBAP_FILTER_PHOTO
                     | PBAP_FILTER_ADR | PBAP_FILTER_EMAIL | PBAP_FILTER_TEL | PBAP_FILTER_NICKNAME;
-    private static final int PBAP_V1_2 = 0x0102;
     private static final int L2CAP_INVALID_PSM = -1;
 
     public static final String PB_PATH = "telecom/pb.vcf";
@@ -99,6 +108,7 @@
     public static final String ICH_PATH = "telecom/ich.vcf";
     public static final String OCH_PATH = "telecom/och.vcf";
 
+    public static final int PBAP_V1_2 = 0x0102;
     public static final byte VCARD_TYPE_21 = 0;
     public static final byte VCARD_TYPE_30 = 1;
 
@@ -194,17 +204,17 @@
                     }
                 } else {
                     Log.w(TAG, "Socket CONNECT Failure ");
-                    mPbapClientStateMachine.obtainMessage(
-                            PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
+                    mPbapClientStateMachine.sendMessage(
+                            PbapClientStateMachine.MSG_CONNECTION_FAILED);
                     return;
                 }
 
                 if (connectObexSession()) {
-                    mPbapClientStateMachine.obtainMessage(
-                            PbapClientStateMachine.MSG_CONNECTION_COMPLETE).sendToTarget();
+                    mPbapClientStateMachine.sendMessage(
+                            PbapClientStateMachine.MSG_CONNECTION_COMPLETE);
                 } else {
-                    mPbapClientStateMachine.obtainMessage(
-                            PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
+                    mPbapClientStateMachine.sendMessage(
+                            PbapClientStateMachine.MSG_CONNECTION_FAILED);
                 }
                 break;
 
@@ -234,34 +244,16 @@
                 removeAccount(mAccount);
                 removeCallLog(mAccount);
 
-                mPbapClientStateMachine.obtainMessage(PbapClientStateMachine.MSG_CONNECTION_CLOSED)
-                    .sendToTarget();
+                mPbapClientStateMachine.sendMessage(PbapClientStateMachine.MSG_CONNECTION_CLOSED);
                 break;
 
             case MSG_DOWNLOAD:
-                try {
-                    mAccountCreated = addAccount(mAccount);
-                    if (!mAccountCreated) {
-                        Log.e(TAG, "Account creation failed.");
-                        return;
-                    }
-                    // Start at contact 1 to exclued Owner Card PBAP 1.1 sec 3.1.5.2
-                    BluetoothPbapRequestPullPhoneBook request =
-                            new BluetoothPbapRequestPullPhoneBook(PB_PATH, mAccount,
-                                    PBAP_REQUESTED_FIELDS, VCARD_TYPE_30, 0, 1);
-                    request.execute(mObexSession);
-                    PhonebookPullRequest processor =
-                            new PhonebookPullRequest(mPbapClientStateMachine.getContext(),
-                                    mAccount);
-                    processor.setResults(request.getList());
-                    processor.onPullComplete();
-                    HashMap<String, Integer> callCounter = new HashMap<>();
-                    downloadCallLog(MCH_PATH, callCounter);
-                    downloadCallLog(ICH_PATH, callCounter);
-                    downloadCallLog(OCH_PATH, callCounter);
-                } catch (IOException e) {
-                    Log.w(TAG, "DOWNLOAD_CONTACTS Failure" + e.toString());
-                }
+                downloadContacts();
+
+                HashMap<String, Integer> callCounter = new HashMap<>();
+                downloadCallLog(MCH_PATH, callCounter);
+                downloadCallLog(ICH_PATH, callCounter);
+                downloadCallLog(OCH_PATH, callCounter);
                 break;
 
             default:
@@ -272,19 +264,19 @@
 
     /* Utilize SDP, if available, to create a socket connection over L2CAP, RFCOMM specified
      * channel, or RFCOMM default channel. */
-    private boolean connectSocket() {
+    private synchronized boolean connectSocket() {
         try {
             /* Use BluetoothSocket to connect */
             if (mPseRec == null) {
                 // BackWardCompatability: Fall back to create RFCOMM through UUID.
-                Log.v(TAG, "connectSocket: UUID: " + BluetoothUuid.PBAP_PSE.getUuid());
+                if (VDBG) Log.v(TAG, "connectSocket: UUID: " + BluetoothUuid.PBAP_PSE.getUuid());
                 mSocket =
                         mDevice.createRfcommSocketToServiceRecord(BluetoothUuid.PBAP_PSE.getUuid());
             } else if (mPseRec.getL2capPsm() != L2CAP_INVALID_PSM) {
-                Log.v(TAG, "connectSocket: PSM: " + mPseRec.getL2capPsm());
+                if (VDBG) Log.v(TAG, "connectSocket: PSM: " + mPseRec.getL2capPsm());
                 mSocket = mDevice.createL2capSocket(mPseRec.getL2capPsm());
             } else {
-                Log.v(TAG, "connectSocket: channel: " + mPseRec.getRfcommChannelNumber());
+                if (VDBG) Log.v(TAG, "connectSocket: channel: " + mPseRec.getRfcommChannelNumber());
                 mSocket = mDevice.createRfcommSocket(mPseRec.getRfcommChannelNumber());
             }
 
@@ -306,7 +298,7 @@
         boolean connectionSuccessful = false;
 
         try {
-            if (DBG) {
+            if (VDBG) {
                 Log.v(TAG, "Start Obex Client Session");
             }
             BluetoothObexTransport transport = new BluetoothObexTransport(mSocket);
@@ -351,7 +343,7 @@
         this.getLooper().getThread().interrupt();
     }
 
-    private void closeSocket() {
+    private synchronized void closeSocket() {
         try {
             if (mSocket != null) {
                 if (DBG) {
@@ -366,6 +358,51 @@
         }
     }
 
+    void downloadContacts() {
+        try {
+            mAccountCreated = addAccount(mAccount);
+            if (!mAccountCreated) {
+                Log.e(TAG, "Account creation failed.");
+                return;
+            }
+            PhonebookPullRequest processor =
+                    new PhonebookPullRequest(mPbapClientStateMachine.getContext(),
+                            mAccount);
+
+            // Download contacts in batches of size DEFAULT_BATCH_SIZE
+            BluetoothPbapRequestPullPhoneBookSize requestPbSize =
+                    new BluetoothPbapRequestPullPhoneBookSize(PB_PATH,
+                            PBAP_REQUESTED_FIELDS);
+            requestPbSize.execute(mObexSession);
+
+            // "-1" because Owner Card is also included in PhoneBookSize
+            int numberOfContactsRemaining = requestPbSize.getSize() - 1;
+
+            // Start at contact 1 to exclude Owner Card PBAP 1.1 sec 3.1.5.2
+            int startOffset = 1;
+            while ((numberOfContactsRemaining > 0) && (startOffset <= UPPER_LIMIT)) {
+                int numberOfContactsToDownload =
+                        Math.min(Math.min(DEFAULT_BATCH_SIZE, numberOfContactsRemaining),
+                        UPPER_LIMIT - startOffset + 1);
+                BluetoothPbapRequestPullPhoneBook request =
+                        new BluetoothPbapRequestPullPhoneBook(PB_PATH, mAccount,
+                                PBAP_REQUESTED_FIELDS, VCARD_TYPE_30,
+                                numberOfContactsToDownload, startOffset);
+                request.execute(mObexSession);
+                processor.setResults(request.getList());
+                processor.onPullComplete();
+
+                startOffset += numberOfContactsToDownload;
+                numberOfContactsRemaining -= numberOfContactsToDownload;
+            }
+            if ((startOffset > UPPER_LIMIT) && (numberOfContactsRemaining > 0)) {
+                Log.w(TAG, "Download contacts incomplete, index exceeded upper limit.");
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Download contacts failure" + e.toString());
+        }
+    }
+
     void downloadCallLog(String path, HashMap<String, Integer> callCounter) {
         try {
             BluetoothPbapRequestPullPhoneBook request =
@@ -410,8 +447,8 @@
                 }
                 return;
             }
-            String where = Calls.PHONE_ACCOUNT_ID + "=" + account.hashCode();
-            mContext.getContentResolver().delete(CallLog.Calls.CONTENT_URI, where, null);
+            mContext.getContentResolver().delete(CallLog.Calls.CONTENT_URI,
+                    Calls.PHONE_ACCOUNT_ID + "=?", new String[]{mAccount.name});
         } catch (IllegalArgumentException e) {
             Log.d(TAG, "Call Logs could not be deleted, they may not exist yet.");
         }
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientService.java b/src/com/android/bluetooth/pbapclient/PbapClientService.java
index 0a9541c..f150cdd 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientService.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientService.java
@@ -26,12 +26,12 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.provider.CallLog;
-import android.provider.Settings;
 import android.util.Log;
 
 import com.android.bluetooth.R;
-import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.sdp.SdpManager;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -44,14 +44,18 @@
  * @hide
  */
 public class PbapClientService extends ProfileService {
-    private static final boolean DBG = false;
+    private static final boolean DBG = Utils.DBG;
+    private static final boolean VDBG = Utils.VDBG;
+
     private static final String TAG = "PbapClientService";
+    private static final String SERVICE_NAME = "Phonebook Access PCE";
     // MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices.
     private static final int MAXIMUM_DEVICES = 10;
     private Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap =
             new ConcurrentHashMap<>();
     private static PbapClientService sPbapClientService;
     private PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver();
+    private int mSdpHandle = -1;
 
     @Override
     public IProfileServiceBinder initBinder() {
@@ -60,8 +64,8 @@
 
     @Override
     protected boolean start() {
-        if (DBG) {
-            Log.d(TAG, "onStart");
+        if (VDBG) {
+            Log.v(TAG, "onStart");
         }
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
@@ -72,13 +76,17 @@
         } catch (Exception e) {
             Log.w(TAG, "Unable to register pbapclient receiver", e);
         }
+
         removeUncleanAccounts();
+        registerSdpRecord();
         setPbapClientService(this);
         return true;
     }
 
     @Override
     protected boolean stop() {
+        setPbapClientService(null);
+        cleanUpSdpRecord();
         try {
             unregisterReceiver(mPbapBroadcastReceiver);
         } catch (Exception e) {
@@ -87,18 +95,12 @@
         for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) {
             pbapClientStateMachine.doQuit();
         }
+        removeUncleanAccounts();
         return true;
     }
 
-    @Override
-    protected void cleanup() {
-        removeUncleanAccounts();
-        // TODO: Should move to stop()
-        setPbapClientService(null);
-    }
-
     void cleanupDevice(BluetoothDevice device) {
-        Log.w(TAG, "Cleanup device: " + device);
+        if (DBG) Log.d(TAG, "Cleanup device: " + device);
         synchronized (mPbapClientStateMachineMap) {
             PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
             if (pbapClientStateMachine != null) {
@@ -112,24 +114,54 @@
         AccountManager accountManager = AccountManager.get(this);
         Account[] accounts =
                 accountManager.getAccountsByType(getString(R.string.pbap_account_type));
-        Log.w(TAG, "Found " + accounts.length + " unclean accounts");
+        if (VDBG) Log.v(TAG, "Found " + accounts.length + " unclean accounts");
         for (Account acc : accounts) {
             Log.w(TAG, "Deleting " + acc);
+            try {
+                getContentResolver().delete(CallLog.Calls.CONTENT_URI,
+                        CallLog.Calls.PHONE_ACCOUNT_ID + "=?", new String[]{acc.name});
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.");
+            }
             // The device ID is the name of the account.
             accountManager.removeAccountExplicitly(acc);
         }
-        try {
-            getContentResolver().delete(CallLog.Calls.CONTENT_URI, null, null);
-        } catch (IllegalArgumentException e) {
-            Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.");
+    }
+
+    private void registerSdpRecord() {
+        SdpManager sdpManager = SdpManager.getDefaultManager();
+        if (sdpManager == null) {
+            Log.e(TAG, "SdpManager is null");
+            return;
+        }
+        mSdpHandle = sdpManager.createPbapPceRecord(SERVICE_NAME,
+                PbapClientConnectionHandler.PBAP_V1_2);
+    }
+
+    private void cleanUpSdpRecord() {
+        if (mSdpHandle < 0) {
+            Log.e(TAG, "cleanUpSdpRecord, SDP record never created");
+            return;
+        }
+        int sdpHandle = mSdpHandle;
+        mSdpHandle = -1;
+        SdpManager sdpManager = SdpManager.getDefaultManager();
+        if (sdpManager == null) {
+            Log.e(TAG, "cleanUpSdpRecord failed, sdpManager is null, sdpHandle=" + sdpHandle);
+            return;
+        }
+        Log.i(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle);
+        if (!sdpManager.removeSdpRecord(sdpHandle)) {
+            Log.e(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle);
         }
     }
 
+
     private class PbapBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            Log.v(TAG, "onReceive" + action);
+            if (DBG) Log.v(TAG, "onReceive" + action);
             if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                 if (getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) {
@@ -160,7 +192,7 @@
         }
 
         private PbapClientService getService() {
-            if (!Utils.checkCaller()) {
+            if (!com.android.bluetooth.Utils.checkCaller()) {
                 Log.w(TAG, "PbapClient call not allowed for non-active user");
                 return null;
             }
@@ -255,8 +287,8 @@
     }
 
     private static synchronized void setPbapClientService(PbapClientService instance) {
-        if (DBG) {
-            Log.d(TAG, "setPbapClientService(): set to: " + instance);
+        if (VDBG) {
+            Log.v(TAG, "setPbapClientService(): set to: " + instance);
         }
         sPbapClientService = instance;
     }
@@ -266,7 +298,7 @@
             throw new IllegalArgumentException("Null device");
         }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-        Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress());
+        if (DBG) Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress());
         if (getPriority(device) <= BluetoothProfile.PRIORITY_OFF) {
             return false;
         }
@@ -342,11 +374,11 @@
             throw new IllegalArgumentException("Null device");
         }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()), priority);
         if (DBG) {
             Log.d(TAG, "Saved priority " + device + " = " + priority);
         }
+        AdapterService.getAdapterService().getDatabase()
+            .setProfilePriority(device, BluetoothProfile.PBAP_CLIENT, priority);
         return true;
     }
 
@@ -355,10 +387,8 @@
             throw new IllegalArgumentException("Null device");
         }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        int priority = Settings.Global.getInt(getContentResolver(),
-                Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
-                BluetoothProfile.PRIORITY_UNDEFINED);
-        return priority;
+        return AdapterService.getAdapterService().getDatabase()
+                .getProfilePriority(device, BluetoothProfile.PBAP_CLIENT);
     }
 
     @Override
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
index 3cc9094..2eaf8e5 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
@@ -67,7 +67,7 @@
 import java.util.List;
 
 final class PbapClientStateMachine extends StateMachine {
-    private static final boolean DBG = true;
+    private static final boolean DBG = Utils.DBG;
     private static final String TAG = "PbapClientStateMachine";
 
     // Messages for handling connect/disconnect requests.
@@ -126,7 +126,7 @@
     class Disconnected extends State {
         @Override
         public void enter() {
-            Log.d(TAG, "Enter Disconnected: " + getCurrentMessage().what);
+            if (DBG) Log.d(TAG, "Enter Disconnected: " + getCurrentMessage().what);
             onConnectionStateChanged(mCurrentDevice, mMostRecentState,
                     BluetoothProfile.STATE_DISCONNECTED);
             mMostRecentState = BluetoothProfile.STATE_DISCONNECTED;
@@ -224,8 +224,6 @@
                     ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
                     if (DBG) {
                         Log.v(TAG, "Received UUID: " + uuid.toString());
-                    }
-                    if (DBG) {
                         Log.v(TAG, "expected UUID: " + BluetoothUuid.PBAP_PSE.toString());
                     }
                     if (uuid.equals(BluetoothUuid.PBAP_PSE)) {
@@ -250,7 +248,7 @@
     class Disconnecting extends State {
         @Override
         public void enter() {
-            Log.d(TAG, "Enter Disconnecting: " + getCurrentMessage().what);
+            if (DBG) Log.d(TAG, "Enter Disconnecting: " + getCurrentMessage().what);
             onConnectionStateChanged(mCurrentDevice, mMostRecentState,
                     BluetoothProfile.STATE_DISCONNECTING);
             mMostRecentState = BluetoothProfile.STATE_DISCONNECTING;
@@ -295,7 +293,7 @@
     class Connected extends State {
         @Override
         public void enter() {
-            Log.d(TAG, "Enter Connected: " + getCurrentMessage().what);
+            if (DBG) Log.d(TAG, "Enter Connected: " + getCurrentMessage().what);
             onConnectionStateChanged(mCurrentDevice, mMostRecentState,
                     BluetoothProfile.STATE_CONNECTED);
             mMostRecentState = BluetoothProfile.STATE_CONNECTED;
@@ -349,7 +347,7 @@
     }
 
     public void disconnect(BluetoothDevice device) {
-        Log.d(TAG, "Disconnect Request " + device);
+        if (DBG) Log.d(TAG, "Disconnect Request " + device);
         sendMessage(MSG_DISCONNECT, device);
     }
 
@@ -433,7 +431,7 @@
     }
 
     public void dump(StringBuilder sb) {
-        ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
-        ProfileService.println(sb, "StateMachine: " + this.toString());
+        ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice.getAddress() + "("
+                + mCurrentDevice.getName() + ") " + this.toString());
     }
 }
diff --git a/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java b/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java
index 49fbd44..48ccf37 100644
--- a/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java
+++ b/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java
@@ -30,8 +30,8 @@
 
 public class PhonebookPullRequest extends PullRequest {
     private static final int MAX_OPS = 250;
-    private static final boolean VDBG = false;
-    private static final String TAG = "PbapPhonebookPullRequest";
+    private static final boolean VDBG = Utils.VDBG;
+    private static final String TAG = "PbapPbPullRequest";
 
     private final Account mAccount;
     private final Context mContext;
diff --git a/src/com/android/bluetooth/pbapclient/Utils.java b/src/com/android/bluetooth/pbapclient/Utils.java
new file mode 100644
index 0000000..c59d8d5
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/Utils.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+class Utils {
+    static final boolean DBG = false;
+    static final boolean VDBG = false;
+}
diff --git a/src/com/android/bluetooth/sap/SapServer.java b/src/com/android/bluetooth/sap/SapServer.java
index 69f683f..3ff1fc7 100644
--- a/src/com/android/bluetooth/sap/SapServer.java
+++ b/src/com/android/bluetooth/sap/SapServer.java
@@ -11,6 +11,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.drawable.Icon;
 import android.hardware.radio.V1_0.ISap;
 import android.os.Handler;
 import android.os.Handler.Callback;
@@ -252,16 +253,17 @@
             sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, type);
             PendingIntent pIntentDisconnect =
                     PendingIntent.getBroadcast(mContext, type, sapDisconnectIntent, flags);
+            Notification.Action actionDisconnect =
+                   new Notification.Action.Builder(Icon.createWithResource(mContext,
+                   android.R.drawable.stat_sys_data_bluetooth), button, pIntentDisconnect).build();
             notification =
                     new Notification.Builder(mContext, SAP_NOTIFICATION_CHANNEL).setOngoing(true)
-                            .addAction(android.R.drawable.stat_sys_data_bluetooth, button,
-                                    pIntentDisconnect)
+                            .addAction(actionDisconnect)
                             .setContentTitle(title)
                             .setTicker(ticker)
                             .setContentText(text)
                             .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
                             .setAutoCancel(false)
-                            .setPriority(Notification.PRIORITY_MAX)
                             .setOnlyAlertOnce(true)
                             .setLocalOnly(true)
                             .build();
@@ -277,22 +279,24 @@
             PendingIntent pIntentForceDisconnect =
                     PendingIntent.getBroadcast(mContext, SapMessage.DISC_IMMEDIATE,
                             sapForceDisconnectIntent, flags);
+            Notification.Action actionDisconnect = new Notification.Action.Builder(
+                    Icon.createWithResource(mContext, android.R.drawable.stat_sys_data_bluetooth),
+                    mContext.getString(R.string.bluetooth_sap_notif_disconnect_button),
+                    pIntentDisconnect).build();
+            Notification.Action actionForceDisconnect =
+                    new Notification.Action.Builder(Icon.createWithResource(mContext,
+                    android.R.drawable.stat_sys_data_bluetooth),
+                    mContext.getString(R.string.bluetooth_sap_notif_force_disconnect_button),
+                    pIntentForceDisconnect).build();
             notification =
                     new Notification.Builder(mContext, SAP_NOTIFICATION_CHANNEL).setOngoing(true)
-                            .addAction(android.R.drawable.stat_sys_data_bluetooth,
-                                    mContext.getString(
-                                            R.string.bluetooth_sap_notif_disconnect_button),
-                                    pIntentDisconnect)
-                            .addAction(android.R.drawable.stat_sys_data_bluetooth,
-                                    mContext.getString(
-                                            R.string.bluetooth_sap_notif_force_disconnect_button),
-                                    pIntentForceDisconnect)
+                            .addAction(actionDisconnect)
+                            .addAction(actionForceDisconnect)
                             .setContentTitle(title)
                             .setTicker(ticker)
                             .setContentText(text)
                             .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
                             .setAutoCancel(false)
-                            .setPriority(Notification.PRIORITY_MAX)
                             .setOnlyAlertOnce(true)
                             .setLocalOnly(true)
                             .build();
@@ -491,7 +495,7 @@
 
             if (mHandlerThread != null) {
                 try {
-                    mHandlerThread.quit();
+                    mHandlerThread.quitSafely();
                     mHandlerThread.join();
                     mHandlerThread = null;
                 } catch (InterruptedException e) {
diff --git a/src/com/android/bluetooth/sap/SapService.java b/src/com/android/bluetooth/sap/SapService.java
index 66e0b19..995051e 100644
--- a/src/com/android/bluetooth/sap/SapService.java
+++ b/src/com/android/bluetooth/sap/SapService.java
@@ -20,17 +20,17 @@
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.PowerManager;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.sdp.SdpManager;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -539,7 +539,7 @@
     public boolean disconnect(BluetoothDevice device) {
         boolean result = false;
         synchronized (SapService.this) {
-            if (getRemoteDevice().equals(device)) {
+            if (mRemoteDevice != null && mRemoteDevice.equals(device)) {
                 switch (mState) {
                     case BluetoothSap.STATE_CONNECTED:
                         closeConnectionSocket();
@@ -596,19 +596,17 @@
     }
 
     public boolean setPriority(BluetoothDevice device, int priority) {
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothSapPriorityKey(device.getAddress()), priority);
         if (DEBUG) {
             Log.d(TAG, "Saved priority " + device + " = " + priority);
         }
+        AdapterService.getAdapterService().getDatabase()
+                .setProfilePriority(device, BluetoothProfile.SAP, priority);
         return true;
     }
 
     public int getPriority(BluetoothDevice device) {
-        int priority = Settings.Global.getInt(getContentResolver(),
-                Settings.Global.getBluetoothSapPriorityKey(device.getAddress()),
-                BluetoothProfile.PRIORITY_UNDEFINED);
-        return priority;
+        return AdapterService.getAdapterService().getDatabase()
+                .getProfilePriority(device, BluetoothProfile.SAP);
     }
 
     @Override
@@ -784,8 +782,9 @@
 
             if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
                 Log.v(TAG, " - Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY");
-                if (!mIsWaitingAuthorization) {
-                    // this reply is not for us
+
+                int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, -1);
+                if (requestType != BluetoothDevice.REQUEST_TYPE_SIM_ACCESS) {
                     return;
                 }
 
@@ -794,7 +793,7 @@
                 if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
                         BluetoothDevice.CONNECTION_ACCESS_NO)
                         == BluetoothDevice.CONNECTION_ACCESS_YES) {
-                    //bluetooth connection accepted by user
+                    // bluetooth connection accepted by user
                     if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
                         boolean result = mRemoteDevice.setSimAccessPermission(
                                 BluetoothDevice.ACCESS_ALLOWED);
@@ -802,6 +801,9 @@
                             Log.v(TAG, "setSimAccessPermission(ACCESS_ALLOWED) result=" + result);
                         }
                     }
+                    boolean result = setPriority(mRemoteDevice, BluetoothProfile.PRIORITY_ON);
+                    Log.d(TAG, "setPriority ON, result = " + result);
+
                     try {
                         if (mConnSocket != null) {
                             // start obex server and rfcomm connection
@@ -820,6 +822,8 @@
                             Log.v(TAG, "setSimAccessPermission(ACCESS_REJECTED) result=" + result);
                         }
                     }
+                    boolean result = setPriority(mRemoteDevice, BluetoothProfile.PRIORITY_OFF);
+                    Log.d(TAG, "setPriority OFF, result = " + result);
                     // Ensure proper cleanup, and prepare for new connect.
                     mSessionStatusHandler.sendEmptyMessage(MSG_SERVERSESSION_CLOSE);
                 }
diff --git a/src/com/android/bluetooth/sdp/SdpManager.java b/src/com/android/bluetooth/sdp/SdpManager.java
index d0cca25..d5e3770 100644
--- a/src/com/android/bluetooth/sdp/SdpManager.java
+++ b/src/com/android/bluetooth/sdp/SdpManager.java
@@ -85,6 +85,9 @@
     private native int sdpCreateMapMnsRecordNative(String serviceName, int rfcommChannel,
             int l2capPsm, int version, int features);
 
+    private native int sdpCreatePbapPceRecordNative(String serviceName,
+            int version);
+
     private native int sdpCreatePbapPseRecordNative(String serviceName, int rfcommChannel,
             int l2capPsm, int version, int repositories, int features);
 
@@ -537,6 +540,27 @@
         return sdpCreateMapMnsRecordNative(serviceName, rfcommChannel, l2capPsm, version, features);
     }
 
+     /**
+     * Create a Client side Phone Book Access Profile Service Record.
+     * Create the record once, and reuse it for all connections.
+     * If changes to a record is needed remove the old record using {@link removeSdpRecord}
+     * and then create a new one.
+     * @param serviceName   The textual name of the service
+     * @param version       The Profile version number (As specified in the Bluetooth
+     *                      PBAP specification)
+     * @return a handle to the record created. The record can be removed again
+     *          using {@link removeSdpRecord}(). The record is not linked to the
+     *          creation/destruction of BluetoothSockets, hence SDP record cleanup
+     *          is a separate process.
+     */
+    public int createPbapPceRecord(String serviceName, int version) {
+        if (!sNativeAvailable) {
+            throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized");
+        }
+        return sdpCreatePbapPceRecordNative(serviceName, version);
+    }
+
+
     /**
      * Create a Server side Phone Book Access Profile Service Record.
      * Create the record once, and reuse it for all connections.
diff --git a/tests/robotests/Android.mk b/tests/robotests/Android.mk
index 5a6c0bb..73f7706 100644
--- a/tests/robotests/Android.mk
+++ b/tests/robotests/Android.mk
@@ -12,8 +12,7 @@
 
 LOCAL_JAVA_LIBRARIES := \
     junit \
-    platform-robolectric-3.6.1-prebuilt \
-    sdk_vcurrent
+    platform-robolectric-3.6.2-prebuilt
 
 LOCAL_INSTRUMENTATION_FOR := Bluetooth
 LOCAL_MODULE := BluetoothRoboTests
@@ -38,4 +37,4 @@
 
 LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src
 
-include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk
+include prebuilts/misc/common/robolectric/3.6.2/run_robotests.mk
diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk
index ef948a6..24db70f 100644
--- a/tests/unit/Android.mk
+++ b/tests/unit/Android.mk
@@ -15,9 +15,16 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES :=  \
     com.android.emailcommon \
-    android-support-test \
+    androidx.test.rules \
     mockito-target \
-    espresso-intents
+    androidx.test.espresso.intents \
+    gson-prebuilt-jar \
+    bt-androidx-room-migration-nodeps \
+    bt-androidx-room-runtime-nodeps \
+    bt-androidx-room-testing-nodeps
+
+LOCAL_ASSET_DIR += \
+    $(LOCAL_PATH)/src/com/android/bluetooth/btservice/storage/schemas
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -30,3 +37,14 @@
 LOCAL_INSTRUMENTATION_FOR := Bluetooth
 
 include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+
+ROOM_LIBS_PATH := ../../lib/room
+
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+    bt-androidx-room-migration-nodeps:$(ROOM_LIBS_PATH)/room-migration-2.0.0-beta01.jar \
+    bt-androidx-room-runtime-nodeps:$(ROOM_LIBS_PATH)/room-runtime-2.0.0-alpha1.aar \
+    bt-androidx-room-testing-nodeps:$(ROOM_LIBS_PATH)/room-testing-2.0.0-alpha1.aar
+
+include $(BUILD_MULTI_PREBUILT)
diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml
index e344399..10c5d6d 100644
--- a/tests/unit/AndroidManifest.xml
+++ b/tests/unit/AndroidManifest.xml
@@ -54,19 +54,14 @@
     <application
         android:allowBackup="true" >
         <uses-library android:name="android.test.runner" />
-        <uses-permission android:name="android.permission.ACCESS_BLUETOOTH_SHARE" />
         <uses-library android:name="org.apache.http.legacy" android:required="false" />
-        <uses-permission android:name="com.android.permission.WHITELIST_BLUETOOTH_DEVICE" />
-        <path-permission
-                android:pathPrefix="/btopp"
-                android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" />
     </application>
     <!--
     This declares that this application uses the instrumentation test runner targeting
     the package of com.android.bluetooth.  To run the tests use the command:
     "adb shell am instrument -w com.android.bluetooth.tests/android.test.InstrumentationTestRunner"
     -->
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="com.android.bluetooth"
                      android:label="Tests for com.android.bluetooth"/>
 </manifest>
diff --git a/tests/unit/AndroidTest.xml b/tests/unit/AndroidTest.xml
index 230f2b1..6edd9a5 100644
--- a/tests/unit/AndroidTest.xml
+++ b/tests/unit/AndroidTest.xml
@@ -26,7 +26,9 @@
         <option name="teardown-command" value="svc bluetooth enable" />
         <option name="teardown-command" value="settings put global ble_scan_always_enabled 1" />
     </target_preparer>
-
+    <target_preparer class="com.android.tradefed.targetprep.FolderSaver">
+        <option name="device-path" value="/data/vendor/ssrdump" />
+    </target_preparer>
     <option name="test-tag" value="BluetoothInstrumentationTests" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.bluetooth.tests" />
diff --git a/tests/unit/src/com/android/bluetooth/FileSystemWriteTest.java b/tests/unit/src/com/android/bluetooth/FileSystemWriteTest.java
index 7841e80..a0215a8 100644
--- a/tests/unit/src/com/android/bluetooth/FileSystemWriteTest.java
+++ b/tests/unit/src/com/android/bluetooth/FileSystemWriteTest.java
@@ -15,8 +15,8 @@
  */
 package com.android.bluetooth;
 
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Assert;
 import org.junit.Test;
diff --git a/tests/unit/src/com/android/bluetooth/TestUtils.java b/tests/unit/src/com/android/bluetooth/TestUtils.java
index d0e3324..4e6b7ba 100644
--- a/tests/unit/src/com/android/bluetooth/TestUtils.java
+++ b/tests/unit/src/com/android/bluetooth/TestUtils.java
@@ -23,8 +23,9 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ServiceTestRule;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ServiceTestRule;
 
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
@@ -33,9 +34,13 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.internal.util.MockUtil;
 
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.HashMap;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -271,6 +276,42 @@
     }
 
     /**
+     * Read Bluetooth adapter configuration from the filesystem
+     *
+     * @return A {@link HashMap} of Bluetooth configs in the format:
+     *  section -> key1 -> value1
+     *          -> key2 -> value2
+     *  Assume no empty section name, no duplicate keys in the same section
+     */
+    public static HashMap<String, HashMap<String, String>> readAdapterConfig() {
+        HashMap<String, HashMap<String, String>> adapterConfig = new HashMap<>();
+        try (BufferedReader reader =
+                new BufferedReader(new FileReader("/data/misc/bluedroid/bt_config.conf"))) {
+            String section = "";
+            for (String line; (line = reader.readLine()) != null;) {
+                line = line.trim();
+                if (line.isEmpty() || line.startsWith("#")) {
+                    continue;
+                }
+                if (line.startsWith("[")) {
+                    if (line.charAt(line.length() - 1) != ']') {
+                        return null;
+                    }
+                    section = line.substring(1, line.length() - 1);
+                    adapterConfig.put(section, new HashMap<>());
+                } else {
+                    String[] keyValue = line.split("=");
+                    adapterConfig.get(section).put(keyValue[0].trim(),
+                            keyValue.length == 1 ? "" : keyValue[1].trim());
+                }
+            }
+        } catch (IOException e) {
+            return null;
+        }
+        return adapterConfig;
+    }
+
+    /**
      * Helper class used to run synchronously a runnable action on a looper.
      */
     private static final class SyncRunnable implements Runnable {
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
new file mode 100644
index 0000000..a97a5b0
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
@@ -0,0 +1,901 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.a2dp;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.res.Resources;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.R;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class A2dpCodecConfigTest {
+    private Context mTargetContext;
+    private BluetoothDevice mTestDevice;
+    private A2dpCodecConfig mA2dpCodecConfig;
+
+    @Mock private Context mMockContext;
+    @Mock private Resources mMockResources;
+    @Mock private A2dpNativeInterface mA2dpNativeInterface;
+
+    private static final int[] sOptionalCodecTypes = new int[] {
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC
+    };
+
+    // Not use the default value to make sure it reads from config
+    private static final int SBC_PRIORITY_DEFAULT = 1001;
+    private static final int AAC_PRIORITY_DEFAULT = 3001;
+    private static final int APTX_PRIORITY_DEFAULT = 5001;
+    private static final int APTX_HD_PRIORITY_DEFAULT = 7001;
+    private static final int LDAC_PRIORITY_DEFAULT = 9001;
+    private static final int PRIORITY_HIGH = 1000000;
+
+    private static final BluetoothCodecConfig[] sCodecCapabilities = new BluetoothCodecConfig[] {
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     SBC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_MONO
+                                     | BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                     AAC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+                                     APTX_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100
+                                     | BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+                                     APTX_HD_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100
+                                     | BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                                     LDAC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100
+                                     | BluetoothCodecConfig.SAMPLE_RATE_48000
+                                     | BluetoothCodecConfig.SAMPLE_RATE_88200
+                                     | BluetoothCodecConfig.SAMPLE_RATE_96000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16
+                                     | BluetoothCodecConfig.BITS_PER_SAMPLE_24
+                                     | BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0)        // Codec-specific fields
+    };
+
+    private static final BluetoothCodecConfig[] sDefaultCodecConfigs = new BluetoothCodecConfig[] {
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     SBC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                     AAC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+                                     APTX_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+                                     APTX_HD_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                                     LDAC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_96000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0)        // Codec-specific fields
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        // Set up mocks and test assets
+        MockitoAnnotations.initMocks(this);
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_sbc))
+                .thenReturn(SBC_PRIORITY_DEFAULT);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_aac))
+                .thenReturn(AAC_PRIORITY_DEFAULT);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_aptx))
+                .thenReturn(APTX_PRIORITY_DEFAULT);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd))
+                .thenReturn(APTX_HD_PRIORITY_DEFAULT);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_ldac))
+                .thenReturn(LDAC_PRIORITY_DEFAULT);
+
+        mA2dpCodecConfig = new A2dpCodecConfig(mMockContext, mA2dpNativeInterface);
+        mTestDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05");
+
+        doReturn(true).when(mA2dpNativeInterface).setCodecConfigPreference(
+                any(BluetoothDevice.class),
+                any(BluetoothCodecConfig[].class));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+
+    @Test
+    public void testAssignCodecConfigPriorities() {
+        BluetoothCodecConfig[] codecConfigs = mA2dpCodecConfig.codecConfigPriorities();
+        for (BluetoothCodecConfig config : codecConfigs) {
+            switch(config.getCodecType()) {
+                case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
+                    Assert.assertEquals(config.getCodecPriority(), SBC_PRIORITY_DEFAULT);
+                    break;
+                case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
+                    Assert.assertEquals(config.getCodecPriority(), AAC_PRIORITY_DEFAULT);
+                    break;
+                case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
+                    Assert.assertEquals(config.getCodecPriority(), APTX_PRIORITY_DEFAULT);
+                    break;
+                case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
+                    Assert.assertEquals(config.getCodecPriority(), APTX_HD_PRIORITY_DEFAULT);
+                    break;
+                case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
+                    Assert.assertEquals(config.getCodecPriority(), LDAC_PRIORITY_DEFAULT);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Test that we can fallback to default codec by lower the codec priority we changed before.
+     */
+    @Test
+    public void testSetCodecPreference_priorityHighToDefault() {
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, SBC_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, AAC_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, APTX_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, APTX_HD_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+                false);
+    }
+
+    /**
+     * Test that we can change the default codec to another by raising the codec priority.
+     * LDAC is the default highest codec, so no need to test others.
+     */
+    @Test
+    public void testSetCodecPreference_priorityDefaultToRaiseHigh() {
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+                false);
+    }
+
+    @Test
+    public void testSetCodecPreference_prioritySbcHighToOthersHigh() {
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                false);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                true);
+    }
+
+    @Test
+    public void testSetCodecPreference_priorityAacHighToOthersHigh() {
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                false);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+    }
+
+    @Test
+    public void testSetCodecPreference_parametersChangedInSameCodec() {
+        // The SBC default / preferred config is Stereo
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        // SBC Mono is mandatory
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                true);
+
+        // The LDAC default / preferred config within mDefaultCodecConfigs
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+
+        // None for system default (Developer options)
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE,
+                false);
+
+        int unsupportedParameter = 0xc0;
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                unsupportedParameter,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                unsupportedParameter,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                unsupportedParameter,
+                false);
+
+        int multipleSupportedParameters = 0x03;
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                multipleSupportedParameters,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                multipleSupportedParameters,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                multipleSupportedParameters,
+                false);
+    }
+
+    @Test
+    public void testSetCodecPreference_parametersChangedToAnotherCodec() {
+        // different sample rate (44.1 kHz -> 96 kHz)
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        // different bits per channel (16 bits -> 32 bits)
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        // change all PCM parameters
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                true);
+
+        // None for system default (Developer options)
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE,
+                true);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE,
+                true);
+
+        int unsupportedParameter = 0xc0;
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                unsupportedParameter,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                unsupportedParameter,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                unsupportedParameter,
+                false);
+
+        int multipleSupportedParameters = 0x03;
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                multipleSupportedParameters,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                multipleSupportedParameters,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                multipleSupportedParameters,
+                false);
+    }
+
+    @Test
+    public void testSetCodecPreference_ldacCodecSpecificFieldChanged() {
+        int ldacAudioQualityHigh = 1000;
+        int ldacAudioQualityABR = 1003;
+        int sbcCodecSpecificParameter = 0;
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                false);
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityHigh,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                true);
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                sbcCodecSpecificParameter,
+                true);
+
+        // Only LDAC will check the codec specific1 field, but not SBC
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                sbcCodecSpecificParameter,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                true);
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                ldacAudioQualityABR,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                true);
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                ldacAudioQualityHigh,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                sbcCodecSpecificParameter,
+                false);
+    }
+
+    @Test
+    public void testDisableOptionalCodecs() {
+        BluetoothCodecConfig[] codecConfigsArray =
+                new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
+        codecConfigsArray[0] = new BluetoothCodecConfig(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE,
+                0, 0, 0, 0);       // Codec-specific fields
+
+        // shouldn't invoke to native when current codec is SBC
+        mA2dpCodecConfig.disableOptionalCodecs(
+                mTestDevice,
+                getDefaultCodecConfigByType(
+                        BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                        BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+        verify(mA2dpNativeInterface, times(0)).setCodecConfigPreference(mTestDevice,
+                                                                        codecConfigsArray);
+
+        // should invoke to native when current codec is an optional codec
+        int invokedCounter = 0;
+        for (int codecType : sOptionalCodecTypes) {
+            mA2dpCodecConfig.disableOptionalCodecs(
+                    mTestDevice,
+                    getDefaultCodecConfigByType(codecType,
+                                                BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+            verify(mA2dpNativeInterface, times(++invokedCounter))
+                    .setCodecConfigPreference(mTestDevice, codecConfigsArray);
+        }
+    }
+
+    @Test
+    public void testEnableOptionalCodecs() {
+        BluetoothCodecConfig[] codecConfigsArray =
+                new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
+        codecConfigsArray[0] = new BluetoothCodecConfig(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                SBC_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE,
+                0, 0, 0, 0);       // Codec-specific fields
+
+        // should invoke to native when current codec is SBC
+        mA2dpCodecConfig.enableOptionalCodecs(
+                mTestDevice,
+                getDefaultCodecConfigByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+        verify(mA2dpNativeInterface, times(1))
+                .setCodecConfigPreference(mTestDevice, codecConfigsArray);
+
+        // shouldn't invoke to native when current codec is already an optional
+        for (int codecType : sOptionalCodecTypes) {
+            mA2dpCodecConfig.enableOptionalCodecs(
+                    mTestDevice,
+                    getDefaultCodecConfigByType(codecType,
+                                                BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+            verify(mA2dpNativeInterface, times(1))
+                    .setCodecConfigPreference(mTestDevice, codecConfigsArray);
+        }
+    }
+
+    private BluetoothCodecConfig getDefaultCodecConfigByType(int codecType, int codecPriority) {
+        for (BluetoothCodecConfig codecConfig : sDefaultCodecConfigs) {
+            if (codecConfig.getCodecType() != codecType) {
+                continue;
+            }
+            return new BluetoothCodecConfig(
+                    codecConfig.getCodecType(),
+                    (codecPriority != BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT
+                     ? codecPriority : codecConfig.getCodecPriority()),
+                    codecConfig.getSampleRate(), codecConfig.getBitsPerSample(),
+                    codecConfig.getChannelMode(), codecConfig.getCodecSpecific1(),
+                    codecConfig.getCodecSpecific2(), codecConfig.getCodecSpecific3(),
+                    codecConfig.getCodecSpecific4());
+        }
+        Assert.fail("getDefaultCodecConfigByType: No such codecType=" + codecType
+                + " in sDefaultCodecConfigs");
+        return null;
+    }
+
+    private BluetoothCodecConfig getCodecCapabilitiesByType(int codecType) {
+        for (BluetoothCodecConfig codecCapabilities : sCodecCapabilities) {
+            if (codecCapabilities.getCodecType() != codecType) {
+                continue;
+            }
+            return new BluetoothCodecConfig(
+                    codecCapabilities.getCodecType(), codecCapabilities.getCodecPriority(),
+                    codecCapabilities.getSampleRate(), codecCapabilities.getBitsPerSample(),
+                    codecCapabilities.getChannelMode(), codecCapabilities.getCodecSpecific1(),
+                    codecCapabilities.getCodecSpecific2(), codecCapabilities.getCodecSpecific3(),
+                    codecCapabilities.getCodecSpecific4());
+        }
+        Assert.fail("getCodecCapabilitiesByType: No such codecType=" + codecType
+                + " in sCodecCapabilities");
+        return null;
+    }
+
+    private void testCodecParametersChangeHelper(int newCodecType, int oldCodecType,
+            int sampleRate, int bitsPerSample, int channelMode, boolean invokeNative) {
+        BluetoothCodecConfig oldCodecConfig =
+                getDefaultCodecConfigByType(oldCodecType, PRIORITY_HIGH);
+        BluetoothCodecConfig[] newCodecConfigsArray = new BluetoothCodecConfig[] {
+                new BluetoothCodecConfig(newCodecType,
+                                         PRIORITY_HIGH,
+                                         sampleRate, bitsPerSample, channelMode,
+                                         0, 0, 0, 0)       // Codec-specific fields
+        };
+
+        // Test cases: 1. no mandatory; 2. mandatory + old + new; 3. all codecs
+        BluetoothCodecConfig[] minimumCodecsArray;
+        if (!oldCodecConfig.isMandatoryCodec() && !newCodecConfigsArray[0].isMandatoryCodec()) {
+            BluetoothCodecConfig[] optionalCodecsArray;
+            if (oldCodecType != newCodecType) {
+                optionalCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(oldCodecType),
+                        getCodecCapabilitiesByType(newCodecType)
+                };
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                        getCodecCapabilitiesByType(oldCodecType),
+                        getCodecCapabilitiesByType(newCodecType)
+                };
+            } else {
+                optionalCodecsArray = new BluetoothCodecConfig[]
+                        {getCodecCapabilitiesByType(oldCodecType)};
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                        getCodecCapabilitiesByType(oldCodecType)
+                };
+            }
+            BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                                        sCodecCapabilities,
+                                                                        optionalCodecsArray);
+            mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                      codecStatus,
+                                                      newCodecConfigsArray[0]);
+            // no mandatory codec in selectable, and should not apply
+            verify(mA2dpNativeInterface, times(0)).setCodecConfigPreference(mTestDevice,
+                                                                            newCodecConfigsArray);
+
+
+        } else {
+            if (oldCodecType != newCodecType) {
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(oldCodecType),
+                        getCodecCapabilitiesByType(newCodecType)
+                };
+            } else {
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(oldCodecType),
+                };
+            }
+        }
+
+        // 2. mandatory + old + new codecs only
+        BluetoothCodecStatus codecStatus =
+                new BluetoothCodecStatus(oldCodecConfig, sCodecCapabilities, minimumCodecsArray);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
+        verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+        // 3. all codecs were selectable
+        codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                               sCodecCapabilities,
+                                               sCodecCapabilities);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
+        verify(mA2dpNativeInterface, times(invokeNative ? 2 : 0))
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+    }
+
+    private void testCodecSpecificParametersChangeHelper(int newCodecType, int newCodecSpecific,
+            int oldCodecType, int oldCodecSpecific, boolean invokeNative) {
+        BluetoothCodecConfig codecDefaultTemp =
+                getDefaultCodecConfigByType(oldCodecType, PRIORITY_HIGH);
+        BluetoothCodecConfig oldCodecConfig =
+                new BluetoothCodecConfig(codecDefaultTemp.getCodecType(),
+                                         codecDefaultTemp.getCodecPriority(),
+                                         codecDefaultTemp.getSampleRate(),
+                                         codecDefaultTemp.getBitsPerSample(),
+                                         codecDefaultTemp.getChannelMode(),
+                                         oldCodecSpecific, 0, 0, 0);       // Codec-specific fields
+        codecDefaultTemp = getDefaultCodecConfigByType(newCodecType, PRIORITY_HIGH);
+        BluetoothCodecConfig[] newCodecConfigsArray = new BluetoothCodecConfig[] {
+                new BluetoothCodecConfig(codecDefaultTemp.getCodecType(),
+                                         codecDefaultTemp.getCodecPriority(),
+                                         codecDefaultTemp.getSampleRate(),
+                                         codecDefaultTemp.getBitsPerSample(),
+                                         codecDefaultTemp.getChannelMode(),
+                                         newCodecSpecific, 0, 0, 0)       // Codec-specific fields
+        };
+        BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                                    sCodecCapabilities,
+                                                                    sCodecCapabilities);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
+        verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+    }
+
+    private void testCodecPriorityChangeHelper(int newCodecType, int newCodecPriority,
+            int oldCodecType, int oldCodecPriority, boolean shouldApplyWhenAllSelectable) {
+
+        BluetoothCodecConfig[] newCodecConfigsArray =
+                new BluetoothCodecConfig[] {
+                        getDefaultCodecConfigByType(newCodecType, newCodecPriority)
+                };
+        BluetoothCodecConfig oldCodecConfig = getDefaultCodecConfigByType(oldCodecType,
+                                                                          oldCodecPriority);
+
+        // Test cases: 1. no mandatory; 2. no new codec; 3. mandatory + old + new; 4. all codecs
+        BluetoothCodecConfig[] minimumCodecsArray;
+        boolean isMinimumCodecsArraySelectable;
+        if (!oldCodecConfig.isMandatoryCodec()) {
+            if (oldCodecType == newCodecType || newCodecConfigsArray[0].isMandatoryCodec()) {
+                // selectable: {-mandatory, +oldCodec = newCodec}, or
+                // selectable: {-mandatory = newCodec, +oldCodec}. Not applied
+                BluetoothCodecConfig[] poorCodecsArray = new BluetoothCodecConfig[]
+                        {getCodecCapabilitiesByType(oldCodecType)};
+                BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                                            sCodecCapabilities,
+                                                                            poorCodecsArray);
+                mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                          codecStatus,
+                                                          newCodecConfigsArray[0]);
+                verify(mA2dpNativeInterface, times(0))
+                        .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+                // selectable: {+mandatory, +oldCodec = newCodec}, or
+                // selectable: {+mandatory = newCodec, +oldCodec}.
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                        getCodecCapabilitiesByType(oldCodecType)
+                };
+            } else {
+                // selectable: {-mandatory, +oldCodec, +newCodec}. Not applied
+                BluetoothCodecConfig[] poorCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(oldCodecType),
+                        getCodecCapabilitiesByType(newCodecType)
+                };
+                BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                                            sCodecCapabilities,
+                                                                            poorCodecsArray);
+                mA2dpCodecConfig.setCodecConfigPreference(
+                        mTestDevice, codecStatus, newCodecConfigsArray[0]);
+                verify(mA2dpNativeInterface, times(0))
+                        .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+                // selectable: {+mandatory, +oldCodec, -newCodec}. Not applied
+                poorCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                        getCodecCapabilitiesByType(oldCodecType)
+                };
+                codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                       sCodecCapabilities,
+                                                       poorCodecsArray);
+                mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                          codecStatus,
+                                                          newCodecConfigsArray[0]);
+                verify(mA2dpNativeInterface, times(0))
+                        .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+                // selectable: {+mandatory, +oldCodec, +newCodec}.
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                      getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                      getCodecCapabilitiesByType(oldCodecType),
+                      getCodecCapabilitiesByType(newCodecType)
+                };
+            }
+            // oldCodec priority should be reset to default, so compare with the default
+            if (newCodecConfigsArray[0].getCodecPriority()
+                    > getDefaultCodecConfigByType(
+                            oldCodecType,
+                            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT).getCodecPriority()
+                    && oldCodecType != newCodecType) {
+                isMinimumCodecsArraySelectable = true;
+            } else {
+                // the old codec was still the highest priority after reset to default
+                isMinimumCodecsArraySelectable = false;
+            }
+        } else if (oldCodecType != newCodecType) {
+            // selectable: {+mandatory = oldCodec, -newCodec}. Not applied
+            BluetoothCodecConfig[] poorCodecsArray = new BluetoothCodecConfig[]
+                    {getCodecCapabilitiesByType(oldCodecType)};
+            BluetoothCodecStatus codecStatus =
+                    new BluetoothCodecStatus(oldCodecConfig, sCodecCapabilities, poorCodecsArray);
+            mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                      codecStatus,
+                                                      newCodecConfigsArray[0]);
+            verify(mA2dpNativeInterface, times(0))
+                    .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+            // selectable: {+mandatory = oldCodec, +newCodec}.
+            minimumCodecsArray = new BluetoothCodecConfig[] {
+                    getCodecCapabilitiesByType(oldCodecType),
+                    getCodecCapabilitiesByType(newCodecType)
+            };
+            isMinimumCodecsArraySelectable = true;
+        } else {
+            // selectable: {mandatory = oldCodec = newCodec}.
+            minimumCodecsArray = new BluetoothCodecConfig[]
+                    {getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC)};
+            isMinimumCodecsArraySelectable = false;
+        }
+
+        // 3. mandatory + old + new codecs only
+        int invokedCounter = (isMinimumCodecsArraySelectable ? 1 : 0);
+        BluetoothCodecStatus codecStatus =
+                new BluetoothCodecStatus(oldCodecConfig, sCodecCapabilities, minimumCodecsArray);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
+        verify(mA2dpNativeInterface, times(invokedCounter))
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+        // 4. all codecs were selectable
+        invokedCounter += (shouldApplyWhenAllSelectable ? 1 : 0);
+        codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                               sCodecCapabilities,
+                                               sCodecCapabilities);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
+        verify(mA2dpNativeInterface, times(invokedCounter))
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
index bef3bdb..78f4ad0 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
@@ -31,14 +31,18 @@
 import android.content.IntentFilter;
 import android.os.Looper;
 import android.os.ParcelUuid;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.avrcp.AvrcpTargetService;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.ServiceFactory;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -73,6 +77,9 @@
 
     @Mock private AdapterService mAdapterService;
     @Mock private A2dpNativeInterface mA2dpNativeInterface;
+    @Mock private DatabaseManager mDatabaseManager;
+    @Mock private AvrcpTargetService mAvrcpTargetService;
+    @Mock private ServiceFactory mFactory;
 
     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
@@ -91,11 +98,13 @@
         TestUtils.setAdapterService(mAdapterService);
         doReturn(MAX_CONNECTED_AUDIO_DEVICES).when(mAdapterService).getMaxConnectedAudioDevices();
         doReturn(false).when(mAdapterService).isQuietModeEnabled();
+        doReturn(mAvrcpTargetService).when(mFactory).getAvrcpTargetService();
 
         mAdapter = BluetoothAdapter.getDefaultAdapter();
 
         startService();
         mA2dpService.mA2dpNativeInterface = mA2dpNativeInterface;
+        mA2dpService.mFactory = mFactory;
 
         // Override the timeout value to speed up the test
         A2dpStateMachine.sConnectTimeoutMs = TIMEOUT_MS;    // 1s
@@ -110,7 +119,6 @@
 
         // Get a device for testing
         mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
-        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_UNDEFINED);
         doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
                 .getBondState(any(BluetoothDevice.class));
         doReturn(new ParcelUuid[]{BluetoothUuid.AudioSink}).when(mAdapterService)
@@ -244,6 +252,8 @@
         });
         // Verify that setActiveDevice(null) was called during shutdown
         verify(mA2dpNativeInterface).setActiveDevice(null);
+        // Verify that storeVolumeForDevice(mTestDevice) was called during shutdown
+        verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
         // Try to restart the service. Note: must be done on the main thread.
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             public void run() {
@@ -253,26 +263,34 @@
     }
 
     /**
-     * Test get/set priority for BluetoothDevice
+     * Test get priority for BluetoothDevice
      */
     @Test
-    public void testGetSetPriority() {
+    public void testGetPriority() {
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         Assert.assertEquals("Initial device priority",
                             BluetoothProfile.PRIORITY_UNDEFINED,
                             mA2dpService.getPriority(mTestDevice));
 
-        Assert.assertTrue(mA2dpService.setPriority(mTestDevice,  BluetoothProfile.PRIORITY_OFF));
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
         Assert.assertEquals("Setting device priority to PRIORITY_OFF",
                             BluetoothProfile.PRIORITY_OFF,
                             mA2dpService.getPriority(mTestDevice));
 
-        Assert.assertTrue(mA2dpService.setPriority(mTestDevice,  BluetoothProfile.PRIORITY_ON));
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         Assert.assertEquals("Setting device priority to PRIORITY_ON",
                             BluetoothProfile.PRIORITY_ON,
                             mA2dpService.getPriority(mTestDevice));
 
-        Assert.assertTrue(mA2dpService.setPriority(mTestDevice,
-                                                   BluetoothProfile.PRIORITY_AUTO_CONNECT));
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
         Assert.assertEquals("Setting device priority to PRIORITY_AUTO_CONNECT",
                             BluetoothProfile.PRIORITY_AUTO_CONNECT,
                             mA2dpService.getPriority(mTestDevice));
@@ -325,9 +343,6 @@
                 badBondState, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
         testOkToConnectCase(mTestDevice,
                 badBondState, badPriorityValue, false);
-        // Restore prirority to undefined for this test device
-        Assert.assertTrue(mA2dpService.setPriority(
-                mTestDevice, BluetoothProfile.PRIORITY_UNDEFINED));
     }
 
 
@@ -337,7 +352,9 @@
     @Test
     public void testOutgoingConnectMissingAudioSinkUuid() {
         // Update the device priority so okToConnect() returns true
-        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
@@ -358,7 +375,9 @@
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
         // Set the device priority to PRIORITY_OFF so connect() should fail
-        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_OFF);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
 
         // Send a connect request
         Assert.assertFalse("Connect expected to fail", mA2dpService.connect(mTestDevice));
@@ -370,7 +389,9 @@
     @Test
     public void testOutgoingConnectTimeout() {
         // Update the device priority so okToConnect() returns true
-        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
@@ -399,7 +420,9 @@
         A2dpStackEvent connCompletedEvent;
 
         // Update the device priority so okToConnect() returns true
-        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
@@ -468,7 +491,9 @@
         for (int i = 0; i < MAX_CONNECTED_AUDIO_DEVICES; i++) {
             BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
             testDevices[i] = testDevice;
-            mA2dpService.setPriority(testDevice, BluetoothProfile.PRIORITY_ON);
+            when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+            when(mDatabaseManager.getProfilePriority(testDevice, BluetoothProfile.A2DP))
+                    .thenReturn(BluetoothProfile.PRIORITY_ON);
             // Send a connect request
             Assert.assertTrue("Connect failed", mA2dpService.connect(testDevice));
             // Verify the connection state broadcast, and that we are in Connecting state
@@ -494,7 +519,9 @@
 
         // Prepare and connect the extra test device. The connect request should fail
         extraTestDevice = TestUtils.getTestDevice(mAdapter, MAX_CONNECTED_AUDIO_DEVICES);
-        mA2dpService.setPriority(extraTestDevice, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(extraTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         // Send a connect request
         Assert.assertFalse("Connect expected to fail", mA2dpService.connect(extraTestDevice));
     }
@@ -508,7 +535,9 @@
         A2dpStackEvent stackEvent;
 
         // Update the device priority so okToConnect() returns true
-        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
@@ -586,7 +615,9 @@
                                                                     codecsSelectableCapabilities);
 
         // Update the device priority so okToConnect() returns true
-        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
@@ -649,7 +680,9 @@
         A2dpStackEvent stackEvent;
 
         // Update the device priority so okToConnect() returns true
-        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
@@ -706,7 +739,9 @@
         A2dpStackEvent stackEvent;
 
         // Update the device priority so okToConnect() returns true
-        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
@@ -744,15 +779,173 @@
         Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice));
     }
 
+    /**
+     * Test that whether active device been removed after enable silence mode
+     */
+    @Test
+    public void testSetSilenceMode() {
+        BluetoothDevice otherDevice = mAdapter.getRemoteDevice("05:04:03:02:01:00");
+        connectDevice(mTestDevice);
+        connectDevice(otherDevice);
+        doReturn(true).when(mA2dpNativeInterface).setActiveDevice(any(BluetoothDevice.class));
+        doReturn(true).when(mA2dpNativeInterface).setSilenceDevice(any(BluetoothDevice.class),
+                anyBoolean());
+
+        // Test whether active device been removed after enable silence mode.
+        Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice));
+        Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
+        Assert.assertTrue(mA2dpService.setSilenceMode(mTestDevice, true));
+        verify(mA2dpNativeInterface).setSilenceDevice(mTestDevice, true);
+        verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
+        Assert.assertNull(mA2dpService.getActiveDevice());
+
+        // Test whether active device been resumeed after disable silence mode.
+        Assert.assertTrue(mA2dpService.setSilenceMode(mTestDevice, false));
+        verify(mA2dpNativeInterface).setSilenceDevice(mTestDevice, false);
+        verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
+        Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
+
+        // Test that active device should not be changed when silence a non-active device
+        Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice));
+        Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
+        Assert.assertTrue(mA2dpService.setSilenceMode(otherDevice, true));
+        verify(mA2dpNativeInterface).setSilenceDevice(otherDevice, true);
+        verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
+        Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
+
+        // Test that active device should not be changed when another device exits silence mode
+        Assert.assertTrue(mA2dpService.setSilenceMode(otherDevice, false));
+        verify(mA2dpNativeInterface).setSilenceDevice(otherDevice, false);
+        verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
+        Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
+    }
+
+    /**
+     * Test that whether updateOptionalCodecsSupport() method is working as intended
+     * when a Bluetooth device is connected with A2DP.
+     */
+    @Test
+    public void testUpdateOptionalCodecsSupport() {
+        int verifySupportTime = 0;
+        int verifyNotSupportTime = 0;
+        int verifyEnabledTime = 0;
+        // Test for device supports optional codec
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, true,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+                ++verifySupportTime, verifyNotSupportTime, ++verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, true,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+                ++verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, true,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+                ++verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, true,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+                verifySupportTime, verifyNotSupportTime, ++verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, true,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+                verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, true,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+                verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, true,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+                ++verifySupportTime, verifyNotSupportTime, ++verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, true,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+                ++verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, true,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+                ++verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+
+        // Test for device not supports optional codec
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, false,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+                verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, false,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+                verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, false,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+                verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, false,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+                verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, false,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+                verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, false,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+                verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, false,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+                verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, false,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+                verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+        testUpdateOptionalCodecsSupportCase(
+                BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, false,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+                verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+    }
+
+    /**
+     * Test that volume level of previous active device will be stored after set active device.
+     */
+    @Test
+    public void testStoreVolumeAfterSetActiveDevice() {
+        BluetoothDevice otherDevice = mAdapter.getRemoteDevice("05:04:03:02:01:00");
+        connectDevice(otherDevice);
+        connectDevice(mTestDevice);
+        doReturn(true).when(mA2dpNativeInterface).setActiveDevice(any(BluetoothDevice.class));
+        doReturn(true).when(mA2dpNativeInterface).setActiveDevice(null);
+        Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice));
+
+        // Test volume stored for previous active device an adjust for current active device
+        Assert.assertTrue(mA2dpService.setActiveDevice(otherDevice));
+        verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
+        verify(mAvrcpTargetService).getRememberedVolumeForDevice(otherDevice);
+
+        // Test volume store for previous active device when set active device to null
+        Assert.assertTrue(mA2dpService.setActiveDevice(null));
+        verify(mAvrcpTargetService).storeVolumeForDevice(otherDevice);
+    }
+
     private void connectDevice(BluetoothDevice device) {
+        connectDeviceWithCodecStatus(device, null);
+    }
+
+    private void connectDeviceWithCodecStatus(BluetoothDevice device,
+            BluetoothCodecStatus codecStatus) {
         A2dpStackEvent connCompletedEvent;
 
         List<BluetoothDevice> prevConnectedDevices = mA2dpService.getConnectedDevices();
 
         // Update the device priority so okToConnect() returns true
-        mA2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(device);
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(device);
+        doReturn(true).when(mA2dpNativeInterface).setCodecConfigPreference(
+                any(BluetoothDevice.class), any(BluetoothCodecConfig[].class));
 
         // Send a connect request
         Assert.assertTrue("Connect failed", mA2dpService.connect(device));
@@ -763,6 +956,10 @@
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
                             mA2dpService.getConnectionState(device));
 
+        if (codecStatus != null) {
+            generateCodecMessageFromNative(device, codecStatus);
+        }
+
         // Send a message to trigger connection completed
         connCompletedEvent = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
         connCompletedEvent.device = device;
@@ -862,7 +1059,9 @@
     private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
             boolean expected) {
         doReturn(bondState).when(mAdapterService).getBondState(device);
-        Assert.assertTrue(mA2dpService.setPriority(device, priority));
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.A2DP))
+                .thenReturn(priority);
 
         // Test when the AdapterService is in non-quiet mode: the result should not depend
         // on whether the connection request is outgoing or incoming.
@@ -876,4 +1075,85 @@
         Assert.assertEquals(expected, mA2dpService.okToConnect(device, true));  // Outgoing
         Assert.assertEquals(false, mA2dpService.okToConnect(device, false)); // Incoming
     }
+
+    /**
+     * Helper function to test updateOptionalCodecsSupport() method
+     *
+     * @param previousSupport previous optional codec support status
+     * @param support new optional codec support status
+     * @param previousEnabled previous optional codec enable status
+     * @param verifySupportTime verify times of optional codec set to support
+     * @param verifyNotSupportTime verify times of optional codec set to not support
+     * @param verifyEnabledTime verify times of optional codec set to enabled
+     */
+    private void testUpdateOptionalCodecsSupportCase(int previousSupport, boolean support,
+            int previousEnabled, int verifySupportTime, int verifyNotSupportTime,
+            int verifyEnabledTime) {
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        doReturn(true).when(mA2dpNativeInterface).setActiveDevice(any(BluetoothDevice.class));
+
+        BluetoothCodecConfig codecConfigSbc =
+                new BluetoothCodecConfig(
+                        BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                        BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                        BluetoothCodecConfig.SAMPLE_RATE_44100,
+                        BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                        BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                        0, 0, 0, 0);       // Codec-specific fields
+        BluetoothCodecConfig codecConfigAac =
+                new BluetoothCodecConfig(
+                        BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                        BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                        BluetoothCodecConfig.SAMPLE_RATE_44100,
+                        BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                        BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                        0, 0, 0, 0);       // Codec-specific fields
+
+        BluetoothCodecConfig[] codecsLocalCapabilities;
+        BluetoothCodecConfig[] codecsSelectableCapabilities;
+        if (support) {
+            codecsLocalCapabilities = new BluetoothCodecConfig[2];
+            codecsSelectableCapabilities = new BluetoothCodecConfig[2];
+            codecsLocalCapabilities[0] = codecConfigSbc;
+            codecsLocalCapabilities[1] = codecConfigAac;
+            codecsSelectableCapabilities[0] = codecConfigSbc;
+            codecsSelectableCapabilities[1] = codecConfigAac;
+        } else {
+            codecsLocalCapabilities = new BluetoothCodecConfig[1];
+            codecsSelectableCapabilities = new BluetoothCodecConfig[1];
+            codecsLocalCapabilities[0] = codecConfigSbc;
+            codecsSelectableCapabilities[0] = codecConfigSbc;
+        }
+        BluetoothCodecConfig[] badCodecsSelectableCapabilities;
+        badCodecsSelectableCapabilities = new BluetoothCodecConfig[1];
+        badCodecsSelectableCapabilities[0] = codecConfigAac;
+
+        BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(codecConfigSbc,
+                codecsLocalCapabilities, codecsSelectableCapabilities);
+        BluetoothCodecStatus badCodecStatus = new BluetoothCodecStatus(codecConfigAac,
+                codecsLocalCapabilities, badCodecsSelectableCapabilities);
+
+        when(mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice))
+                .thenReturn(previousSupport);
+        when(mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice))
+                .thenReturn(previousEnabled);
+
+        // Generate connection request from native with bad codec status
+        connectDeviceWithCodecStatus(mTestDevice, badCodecStatus);
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED);
+
+        // Generate connection request from native with good codec status
+        connectDeviceWithCodecStatus(mTestDevice, codecStatus);
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED);
+
+        // Check optional codec status is set properly
+        verify(mDatabaseManager, times(verifyNotSupportTime)).setA2dpSupportsOptionalCodecs(
+                mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
+        verify(mDatabaseManager, times(verifySupportTime)).setA2dpSupportsOptionalCodecs(
+                mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
+        verify(mDatabaseManager, times(verifyEnabledTime)).setA2dpOptionalCodecsEnabled(
+                mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
+    }
 }
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
index 0b825bf..cacd71a 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
@@ -20,14 +20,17 @@
 
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
 import android.os.HandlerThread;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
@@ -54,6 +57,9 @@
     private BluetoothDevice mTestDevice;
     private static final int TIMEOUT_MS = 1000;    // 1s
 
+    private BluetoothCodecConfig mCodecConfigSbc;
+    private BluetoothCodecConfig mCodecConfigAac;
+
     @Mock private AdapterService mAdapterService;
     @Mock private A2dpService mA2dpService;
     @Mock private A2dpNativeInterface mA2dpNativeInterface;
@@ -72,6 +78,22 @@
         // Get a device for testing
         mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
 
+        // Set up sample codec config
+        mCodecConfigSbc = new BluetoothCodecConfig(
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+            BluetoothCodecConfig.SAMPLE_RATE_44100,
+            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+            0, 0, 0, 0);       // Codec-specific fields
+        mCodecConfigAac = new BluetoothCodecConfig(
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+            BluetoothCodecConfig.SAMPLE_RATE_48000,
+            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+            0, 0, 0, 0);       // Codec-specific fields
+
         // Set up thread and looper
         mHandlerThread = new HandlerThread("A2dpStateMachineTestHandlerThread");
         mHandlerThread.start();
@@ -255,4 +277,87 @@
         Assert.assertThat(mA2dpStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(A2dpStateMachine.Disconnected.class));
     }
+
+    /**
+     * Test that codec config change been reported to A2dpService properly.
+     */
+    @Test
+    public void testProcessCodecConfigEvent() {
+        testProcessCodecConfigEventCase(false);
+    }
+
+    /**
+     * Test that codec config change been reported to A2dpService properly when
+     * A2DP hardware offloading is enabled.
+     */
+    @Test
+    public void testProcessCodecConfigEvent_OffloadEnabled() {
+        testProcessCodecConfigEventCase(true);
+    }
+
+    /**
+     * Helper methold to test processCodecConfigEvent()
+     */
+    public void testProcessCodecConfigEventCase(boolean offloadEnabled) {
+        if (offloadEnabled) {
+            mA2dpStateMachine.mA2dpOffloadEnabled = true;
+        }
+
+        doNothing().when(mA2dpService).codecConfigUpdated(any(BluetoothDevice.class),
+                any(BluetoothCodecStatus.class), anyBoolean());
+        doNothing().when(mA2dpService).updateOptionalCodecsSupport(any(BluetoothDevice.class));
+        allowConnection(true);
+
+        BluetoothCodecConfig[] codecsSelectableSbc;
+        codecsSelectableSbc = new BluetoothCodecConfig[1];
+        codecsSelectableSbc[0] = mCodecConfigSbc;
+
+        BluetoothCodecConfig[] codecsSelectableSbcAac;
+        codecsSelectableSbcAac = new BluetoothCodecConfig[2];
+        codecsSelectableSbcAac[0] = mCodecConfigSbc;
+        codecsSelectableSbcAac[1] = mCodecConfigAac;
+
+        BluetoothCodecStatus codecStatusSbcAndSbc = new BluetoothCodecStatus(mCodecConfigSbc,
+                codecsSelectableSbcAac, codecsSelectableSbc);
+        BluetoothCodecStatus codecStatusSbcAndSbcAac = new BluetoothCodecStatus(mCodecConfigSbc,
+                codecsSelectableSbcAac, codecsSelectableSbcAac);
+        BluetoothCodecStatus codecStatusAacAndSbcAac = new BluetoothCodecStatus(mCodecConfigAac,
+                codecsSelectableSbcAac, codecsSelectableSbcAac);
+
+        // Set default codec status when device disconnected
+        // Selected codec = SBC, selectable codec = SBC
+        mA2dpStateMachine.processCodecConfigEvent(codecStatusSbcAndSbc);
+        verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusSbcAndSbc, false);
+
+        // Inject an event to change state machine to connected state
+        A2dpStackEvent connStCh =
+                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connStCh.device = mTestDevice;
+        connStCh.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED;
+        mA2dpStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, connStCh);
+
+        // Verify that the expected number of broadcasts are executed:
+        // - two calls to broadcastConnectionState(): Disconnected -> Conecting -> Connected
+        // - one call to broadcastAudioState() when entering Connected state
+        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mA2dpService, timeout(TIMEOUT_MS).times(2)).sendBroadcast(intentArgument2.capture(),
+                anyString());
+
+        // Verify that state machine update optional codec when enter connected state
+        verify(mA2dpService, times(1)).updateOptionalCodecsSupport(mTestDevice);
+
+        // Change codec status when device connected.
+        // Selected codec = SBC, selectable codec = SBC+AAC
+        mA2dpStateMachine.processCodecConfigEvent(codecStatusSbcAndSbcAac);
+        if (!offloadEnabled) {
+            verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusSbcAndSbcAac, true);
+        }
+        verify(mA2dpService, times(2)).updateOptionalCodecsSupport(mTestDevice);
+
+        // Update selected codec with selectable codec unchanged.
+        // Selected codec = AAC, selectable codec = SBC+AAC
+        mA2dpStateMachine.processCodecConfigEvent(codecStatusAacAndSbcAac);
+        verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusAacAndSbcAac, false);
+        verify(mA2dpService, times(2)).updateOptionalCodecsSupport(mTestDevice);
+    }
 }
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
index 2735c11..d82d28b 100644
--- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
@@ -15,16 +15,22 @@
  */
 package com.android.bluetooth.a2dpsink;
 
+import static org.mockito.Mockito.*;
+
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -46,6 +52,7 @@
     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
     @Mock private AdapterService mAdapterService;
+    @Mock private DatabaseManager mDatabaseManager;
 
     @Before
     public void setUp() throws Exception {
@@ -60,6 +67,7 @@
         // Try getting the Bluetooth adapter
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         Assert.assertNotNull(mAdapter);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
     }
 
     @After
@@ -73,8 +81,43 @@
         TestUtils.clearAdapterService(mAdapterService);
     }
 
+    private BluetoothDevice makeBluetoothDevice(String address) {
+        return mAdapter.getRemoteDevice(address);
+    }
+
+    /**
+     * Mock the priority of a bluetooth device
+     *
+     * @param device - The bluetooth device you wish to mock the priority of
+     * @param priority - The priority value you want the device to have
+     */
+    private void mockDevicePriority(BluetoothDevice device, int priority) {
+        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.A2DP_SINK))
+                .thenReturn(priority);
+    }
+
     @Test
     public void testInitialize() {
         Assert.assertNotNull(A2dpSinkService.getA2dpSinkService());
     }
+
+    /**
+     * Test that a PRIORITY_ON device is connected to
+     */
+    @Test
+    public void testConnect() {
+        BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
+        mockDevicePriority(device, BluetoothProfile.PRIORITY_ON);
+        Assert.assertTrue(mService.connect(device));
+    }
+
+    /**
+     * Test that a PRIORITY_OFF device is not connected to
+     */
+    @Test
+    public void testConnectPriorityOffDevice() {
+        BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
+        mockDevicePriority(device, BluetoothProfile.PRIORITY_OFF);
+        Assert.assertFalse(mService.connect(device));
+    }
 }
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
index eddab48..53e8419 100644
--- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
@@ -24,9 +24,10 @@
 import android.media.AudioManager;
 import android.os.HandlerThread;
 import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 
@@ -47,7 +48,7 @@
 
     @Mock private Context mMockContext;
 
-    @Mock private A2dpSinkStateMachine mMockA2dpSink;
+    @Mock private A2dpSinkService mMockA2dpSink;
 
     @Mock private AudioManager mMockAudioManager;
 
@@ -189,7 +190,8 @@
                 mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
                         AudioManager.AUDIOFOCUS_LOSS_TRANSIENT));
         verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
-        verify(mMockA2dpSink, times(1)).informAudioFocusStateNative(0);
+        verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(0);
+        verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0);
     }
 
     @Test
diff --git a/tests/unit/src/com/android/bluetooth/avrcp/AvrcpTest.java b/tests/unit/src/com/android/bluetooth/avrcp/AvrcpTest.java
deleted file mode 100644
index 5af45e1..0000000
--- a/tests/unit/src/com/android/bluetooth/avrcp/AvrcpTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.android.bluetooth.avrcp;
-
-import static org.mockito.Mockito.*;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.media.AudioManager;
-import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-/**
- * Unit tests for {@link Avrcp}
- */
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class AvrcpTest {
-    @Test
-    public void testCanStart() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
-        Avrcp a = Avrcp.make(InstrumentationRegistry.getTargetContext());
-    }
-
-    @Test
-    public void testFailedBrowseStart() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
-        Context mockContext = mock(Context.class);
-        AudioManager mockAudioManager = mock(AudioManager.class);
-        PackageManager mockPackageManager = mock(PackageManager.class);
-
-        when(mockAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)).thenReturn(100);
-
-        when(mockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mockAudioManager);
-
-        when(mockContext.getApplicationContext()).thenReturn(mockContext);
-        when(mockContext.getPackageManager()).thenReturn(mockPackageManager);
-
-
-        // Call to get the BrowsableMediaPlayers
-        // We must return at least one to try to startService
-        List<ResolveInfo> resInfos = new ArrayList<ResolveInfo>();
-
-        ServiceInfo fakeService = new ServiceInfo();
-        fakeService.name = ".browse.MediaBrowserService";
-        fakeService.packageName = "com.test.android.fake";
-
-        ResolveInfo fakePackage = new ResolveInfo();
-        fakePackage.serviceInfo = fakeService;
-        fakePackage.nonLocalizedLabel = "Fake Package";
-        resInfos.add(fakePackage);
-        when(mockPackageManager.queryIntentServices(isA(Intent.class), anyInt())).thenReturn(
-                resInfos);
-
-        when(mockContext.startService(isA(Intent.class))).thenThrow(new SecurityException("test"));
-
-        // Make calls start() which calls buildMediaPlayersList() which should
-        // try to start the service?
-        try {
-            Avrcp a = Avrcp.make(mockContext);
-        } catch (SecurityException e) {
-            Assert.fail(
-                    "Threw SecurityException instead of protecting against it: " + e.toString());
-        }
-    }
-}
diff --git a/tests/unit/src/com/android/bluetooth/avrcp/EvictingQueueTest.java b/tests/unit/src/com/android/bluetooth/avrcp/EvictingQueueTest.java
deleted file mode 100644
index ff44f06..0000000
--- a/tests/unit/src/com/android/bluetooth/avrcp/EvictingQueueTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.android.bluetooth.avrcp;
-
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- *  Unit tests for {@link EvictingQueue}.
- */
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class EvictingQueueTest {
-    @Test
-    public void testEvictingQueue_canAddItems() {
-        EvictingQueue<Integer> e = new EvictingQueue<Integer>(10);
-
-        e.add(1);
-
-        Assert.assertEquals((long) e.size(), (long) 1);
-    }
-
-    @Test
-    public void testEvictingQueue_maxItems() {
-        EvictingQueue<Integer> e = new EvictingQueue<Integer>(5);
-
-        e.add(1);
-        e.add(2);
-        e.add(3);
-        e.add(4);
-        e.add(5);
-        e.add(6);
-
-        Assert.assertEquals((long) e.size(), (long) 5);
-        // Items drop off the front
-        Assert.assertEquals((long) e.peek(), (long) 2);
-    }
-
-    @Test
-    public void testEvictingQueue_frontDrop() {
-        EvictingQueue<Integer> e = new EvictingQueue<Integer>(5);
-
-        e.add(1);
-        e.add(2);
-        e.add(3);
-        e.add(4);
-        e.add(5);
-
-        Assert.assertEquals((long) e.size(), (long) 5);
-
-        e.addFirst(6);
-
-        Assert.assertEquals((long) e.size(), (long) 5);
-        Assert.assertEquals((long) e.peek(), (long) 1);
-    }
-}
diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java
index 8bf4679..e9440f5 100644
--- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java
@@ -17,10 +17,11 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
new file mode 100644
index 0000000..4c77e0a
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.avrcpcontroller;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAvrcpController;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.os.Looper;
+import android.support.v4.media.session.MediaControllerCompat;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.R;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.ProfileService;
+
+import org.hamcrest.core.IsInstanceOf;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AvrcpControllerStateMachineTest {
+    private static final int ASYNC_CALL_TIMEOUT_MILLIS = 100;
+    private static final int CONNECT_TIMEOUT_TEST_MILLIS = 1000;
+    private static final int KEY_DOWN = 0;
+    private static final int KEY_UP = 1;
+    private BluetoothAdapter mAdapter;
+    private AvrcpControllerStateMachine mAvrcpControllerStateMachine;
+    private Context mTargetContext;
+    private BluetoothDevice mTestDevice;
+    private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
+    private byte[] mTestAddress = new byte[]{00, 01, 02, 03, 04, 05};
+
+    @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+    @Mock
+    private AdapterService mAdapterService;
+    @Mock
+    private AudioManager mAudioManager;
+    @Mock
+    private AvrcpControllerService mAvrcpControllerService;
+
+    AvrcpControllerStateMachine mAvrcpStateMachine;
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when AVRCP Controller is not enabled",
+                mTargetContext.getResources().getBoolean(
+                        R.bool.profile_supported_avrcp_controller));
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        Assert.assertNotNull(Looper.myLooper());
+
+        // Setup mocks and test assets
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        TestUtils.startService(mServiceRule, AvrcpControllerService.class);
+        doReturn(mTargetContext.getResources()).when(mAvrcpControllerService).getResources();
+        doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt());
+        doReturn(8).when(mAudioManager).getStreamVolume(anyInt());
+        doReturn(true).when(mAudioManager).isVolumeFixed();
+        doReturn(mAudioManager).when(mAvrcpControllerService)
+                .getSystemService(Context.AUDIO_SERVICE);
+
+        // This line must be called to make sure relevant objects are initialized properly
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        // Get a device for testing
+        mTestDevice = mAdapter.getRemoteDevice(mTestAddress);
+        mAvrcpControllerService.start();
+        mAvrcpControllerService.sBrowseTree = new BrowseTree(null);
+        mAvrcpStateMachine = new AvrcpControllerStateMachine(mTestDevice, mAvrcpControllerService);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_avrcp_controller)) {
+            return;
+        }
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    /**
+     * Test to confirm that the state machine is capable of cycling through the 4
+     * connection states, and that upon completion, it cleans up aftwards.
+     */
+    @Test
+    public void testDisconnect() {
+        int numBroadcastsSent = setUpConnectedState(true, true);
+        StackEvent event =
+                StackEvent.connectionStateChanged(false, false);
+
+        mAvrcpStateMachine.disconnect();
+        numBroadcastsSent += 2;
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
+                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+        Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
+                BluetoothDevice.EXTRA_DEVICE));
+        Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
+                mIntentArgument.getValue().getAction());
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
+        Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
+        verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
+    }
+
+    /**
+     * Test to confirm that a control only device can be established (no browsing)
+     */
+    @Test
+    public void testControlOnly() {
+        int numBroadcastsSent = setUpConnectedState(true, false);
+        StackEvent event =
+                StackEvent.connectionStateChanged(false, false);
+        mAvrcpStateMachine.disconnect();
+        numBroadcastsSent += 2;
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
+                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+        Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
+                BluetoothDevice.EXTRA_DEVICE));
+        Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
+                mIntentArgument.getValue().getAction());
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
+        Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
+        verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
+    }
+
+    /**
+     * Test to confirm that a browsing only device can be established (no control)
+     */
+    @Test
+    public void testBrowsingOnly() {
+        Assert.assertEquals(0, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount());
+        int numBroadcastsSent = setUpConnectedState(false, true);
+        Assert.assertEquals(1, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount());
+        StackEvent event =
+                StackEvent.connectionStateChanged(false, false);
+        mAvrcpStateMachine.disconnect();
+        numBroadcastsSent += 2;
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
+                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+        Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
+                BluetoothDevice.EXTRA_DEVICE));
+        Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
+                mIntentArgument.getValue().getAction());
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
+        Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
+        verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
+    }
+
+    /**
+     * Test to make sure the state machine is tracking the correct device
+     */
+    @Test
+    public void testGetDevice() {
+        Assert.assertEquals(mAvrcpStateMachine.getDevice(), mTestDevice);
+    }
+
+    /**
+     * Test that dumpsys will generate information about connected devices
+     */
+    @Test
+    public void testDump() {
+        StringBuilder sb = new StringBuilder();
+        mAvrcpStateMachine.dump(sb);
+        Assert.assertEquals(sb.toString(),
+                "  mDevice: " + mTestDevice.toString()
+                + "(null) name=AvrcpControllerStateMachine state=(null)\n");
+    }
+
+    /**
+     * Test media browser play command
+     */
+    @Test
+    public void testPlay() throws Exception {
+        setUpConnectedState(true, true);
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+
+        //Play
+        transportControls.play();
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_DOWN));
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP));
+    }
+
+    /**
+     * Test media browser pause command
+     */
+    @Test
+    public void testPause() throws Exception {
+        setUpConnectedState(true, true);
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+
+        //Pause
+        transportControls.pause();
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_UP));
+    }
+
+    /**
+     * Test media browser stop command
+     */
+    @Test
+    public void testStop() throws Exception {
+        setUpConnectedState(true, true);
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+
+        //Stop
+        transportControls.stop();
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_STOP), eq(KEY_DOWN));
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_STOP), eq(KEY_UP));
+    }
+
+    /**
+     * Test media browser next command
+     */
+    @Test
+    public void testNext() throws Exception {
+        setUpConnectedState(true, true);
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+
+        //Next
+        transportControls.skipToNext();
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD),
+                eq(KEY_DOWN));
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD), eq(KEY_UP));
+    }
+
+    /**
+     * Test media browser previous command
+     */
+    @Test
+    public void testPrevious() throws Exception {
+        setUpConnectedState(true, true);
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+
+        //Previous
+        transportControls.skipToPrevious();
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD),
+                eq(KEY_DOWN));
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD), eq(KEY_UP));
+    }
+
+    /**
+     * Test media browser fast forward command
+     */
+    @Test
+    public void testFastForward() throws Exception {
+        setUpConnectedState(true, true);
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+
+        //FastForward
+        transportControls.fastForward();
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FF), eq(KEY_DOWN));
+        //Finish FastForwarding
+        transportControls.play();
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FF), eq(KEY_UP));
+    }
+
+    /**
+     * Test media browser rewind command
+     */
+    @Test
+    public void testRewind() throws Exception {
+        setUpConnectedState(true, true);
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+
+        //Rewind
+        transportControls.rewind();
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_REWIND), eq(KEY_DOWN));
+        //Finish Rewinding
+        transportControls.play();
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_REWIND), eq(KEY_UP));
+    }
+
+    /**
+     * Test media browser shuffle command
+     */
+    @Test
+    public void testShuffle() throws Exception {
+        byte[] shuffleSetting = new byte[]{3};
+        byte[] shuffleMode = new byte[]{2};
+
+        setUpConnectedState(true, true);
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+
+        //Shuffle
+        transportControls.setShuffleMode(1);
+        verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
+                .setPlayerApplicationSettingValuesNative(
+                eq(mTestAddress), eq((byte) 1), eq(shuffleSetting), eq(shuffleMode));
+    }
+
+    /**
+     * Test media browser repeat command
+     */
+    @Test
+    public void testRepeat() throws Exception {
+        byte[] repeatSetting = new byte[]{2};
+        byte[] repeatMode = new byte[]{3};
+
+        setUpConnectedState(true, true);
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+
+        //Shuffle
+        transportControls.setRepeatMode(2);
+        verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
+                .setPlayerApplicationSettingValuesNative(
+                eq(mTestAddress), eq((byte) 1), eq(repeatSetting), eq(repeatMode));
+    }
+
+    /**
+     * Test media browsing
+     * Verify that a browse tree is created with the proper root
+     * Verify that a player can be fetched and added to the browse tree
+     * Verify that the contents of a player are fetched upon request
+     */
+    @Test
+    public void testBrowsingCommands() {
+        setUpConnectedState(true, true);
+        final String rootName = "__ROOT__";
+        final String playerName = "Player 1";
+
+        //Get the root of the device
+        BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
+        Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
+
+        //Request fetch the list of players
+        BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
+        mAvrcpStateMachine.requestContents(results);
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
+                eq(0), eq(19));
+
+        //Provide back a player object
+        byte[] playerFeatures =
+                new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
+        AvrcpPlayer playerOne = new AvrcpPlayer(1, playerName, playerFeatures, 1, 1);
+        List<AvrcpPlayer> testPlayers = new ArrayList<>();
+        testPlayers.add(playerOne);
+        mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
+                testPlayers);
+
+        //Verify that the player object is available.
+        mAvrcpStateMachine.requestContents(results);
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
+                eq(1), eq(0));
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
+        playerNodes = mAvrcpStateMachine.findNode(results.getID());
+        Assert.assertEquals(true, results.isCached());
+        Assert.assertEquals("MediaItem{mFlags=1, mDescription=" + playerName + ", null, null}",
+                results.getChildren().get(0).getMediaItem().toString());
+
+        //Fetch contents of that player object
+        BrowseTree.BrowseNode playerOneNode = mAvrcpStateMachine.findNode(
+                results.getChildren().get(0).getID());
+        mAvrcpStateMachine.requestContents(playerOneNode);
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setBrowsedPlayerNative(
+                eq(mTestAddress), eq(1));
+        mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, 5);
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getFolderListNative(eq(mTestAddress),
+                eq(0), eq(4));
+    }
+
+    /**
+     * Test addressed media player changed
+     * Verify when the addressed media player changes browsing data updates
+     * Verify that the contents of a player are fetched upon request
+     */
+    @Test
+    public void testPlayerChanged() {
+        setUpConnectedState(true, true);
+        final String rootName = "__ROOT__";
+        final String playerName = "Player 1";
+
+        //Get the root of the device
+        BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
+        Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
+
+        //Request fetch the list of players
+        BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
+        mAvrcpStateMachine.requestContents(results);
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
+                eq(0), eq(19));
+
+        //Provide back a player object
+        byte[] playerFeatures =
+                new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
+        AvrcpPlayer playerOne = new AvrcpPlayer(1, playerName, playerFeatures, 1, 1);
+        List<AvrcpPlayer> testPlayers = new ArrayList<>();
+        testPlayers.add(playerOne);
+        mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
+                testPlayers);
+
+        //Change players and verify that BT attempts to update the results
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, 4);
+        results = mAvrcpStateMachine.findNode(rootName);
+
+        mAvrcpStateMachine.requestContents(results);
+
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).getPlayerListNative(eq(mTestAddress),
+                eq(0), eq(19));
+    }
+
+    /**
+     * Test that the Now Playing playlist is updated when it changes.
+     */
+    @Test
+    public void testNowPlaying() {
+        setUpConnectedState(true, true);
+        mAvrcpStateMachine.nowPlayingContentChanged();
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getNowPlayingListNative(
+                eq(mTestAddress), eq(0), eq(19));
+    }
+
+    /**
+     * Test that AVRCP events such as playback commands can execute while performing browsing.
+     */
+    @Test
+    public void testPlayWhileBrowsing() {
+        setUpConnectedState(true, true);
+        final String rootName = "__ROOT__";
+        final String playerName = "Player 1";
+
+        //Get the root of the device
+        BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
+        Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
+
+        //Request fetch the list of players
+        BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
+        mAvrcpStateMachine.requestContents(results);
+
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+        transportControls.play();
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_DOWN));
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP));
+    }
+
+    /**
+     * Test that Absolute Volume Registration is working
+     */
+    @Test
+    public void testRegisterAbsVolumeNotification() {
+        setUpConnectedState(true, true);
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION);
+        verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
+                .sendRegisterAbsVolRspNative(any(), anyByte(), eq(127), anyInt());
+    }
+
+    /**
+     * Setup Connected State
+     *
+     * @return number of times mAvrcpControllerService.sendBroadcastAsUser() has been invoked
+     */
+    private int setUpConnectedState(boolean control, boolean browsing) {
+        // Put test state machine into connected state
+        mAvrcpStateMachine.start();
+        Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
+
+        mAvrcpStateMachine.connect(StackEvent.connectionStateChanged(control, browsing));
+        verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
+                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+        Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Connected.class));
+        Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_CONNECTED);
+
+        return BluetoothProfile.STATE_CONNECTED;
+    }
+
+}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java b/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
index 2d01bef..5580155 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
@@ -27,9 +27,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.media.AudioManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
@@ -216,7 +217,8 @@
     @Test
     public void hearingAidActive_clearA2dpAndHeadsetActive() {
         Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
-                mContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+                mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_hearing_aid_profile_supported));
 
         a2dpConnected(mA2dpHeadsetDevice);
         headsetConnected(mA2dpHeadsetDevice);
@@ -234,7 +236,8 @@
     @Test
     public void hearingAidActive_dontSetA2dpAndHeadsetActive() {
         Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
-                mContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+                mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_hearing_aid_profile_supported));
 
         hearingAidActiveDeviceChanged(mHearingAidDevice);
         a2dpConnected(mA2dpHeadsetDevice);
@@ -251,7 +254,8 @@
     @Test
     public void hearingAidActive_setA2dpActiveExplicitly() {
         Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
-                mContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+                mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_hearing_aid_profile_supported));
 
         hearingAidActiveDeviceChanged(mHearingAidDevice);
         a2dpConnected(mA2dpHeadsetDevice);
@@ -271,7 +275,8 @@
     @Test
     public void hearingAidActive_setHeadsetActiveExplicitly() {
         Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
-                mContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+                mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_hearing_aid_profile_supported));
 
         hearingAidActiveDeviceChanged(mHearingAidDevice);
         headsetConnected(mA2dpHeadsetDevice);
diff --git a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
index 2b19a20..e626cbc 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
@@ -17,15 +17,11 @@
 package com.android.bluetooth.btservice;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 import android.app.AlarmManager;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.IBluetoothCallback;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -38,24 +34,42 @@
 import android.os.Process;
 import android.os.SystemProperties;
 import android.os.UserManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
 import android.test.mock.MockContentResolver;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.Utils;
+
+import libcore.util.HexEncoding;
 
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class AdapterServiceTest {
+    private static final String TAG = AdapterServiceTest.class.getSimpleName();
+
     private AdapterService mAdapterService;
 
     private @Mock Context mMockContext;
@@ -73,10 +87,28 @@
     private static final int CONTEXT_SWITCH_MS = 100;
     private static final int ONE_SECOND_MS = 1000;
     private static final int NATIVE_INIT_MS = 8000;
+    private static final int NATIVE_DISABLE_MS = 1000;
 
     private PowerManager mPowerManager;
     private PackageManager mMockPackageManager;
     private MockContentResolver mMockContentResolver;
+    private HashMap<String, HashMap<String, String>> mAdapterConfig;
+
+    @BeforeClass
+    public static void setupClass() {
+        // Bring native layer up and down to make sure config files are properly loaded
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        Assert.assertNotNull(Looper.myLooper());
+        AdapterService adapterService = new AdapterService();
+        adapterService.initNative();
+        adapterService.cleanupNative();
+        HashMap<String, HashMap<String, String>> adapterConfig = TestUtils.readAdapterConfig();
+        Assert.assertNotNull(adapterConfig);
+        Assert.assertNotNull("metrics salt is null: " + adapterConfig.toString(),
+                getMetricsSalt(adapterConfig));
+    }
 
     @Before
     public void setUp() throws PackageManager.NameNotFoundException {
@@ -85,12 +117,8 @@
         }
         Assert.assertNotNull(Looper.myLooper());
 
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mAdapterService = new AdapterService();
-            }
-        });
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> mAdapterService = new AdapterService());
         mMockPackageManager = mock(PackageManager.class);
         mMockContentResolver = new MockContentResolver(mMockContext);
         MockitoAnnotations.initMocks(this);
@@ -107,6 +135,10 @@
         when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
         when(mMockContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mMockAlarmManager);
         when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        doAnswer(invocation -> {
+            Object[] args = invocation.getArguments();
+            return InstrumentationRegistry.getTargetContext().getDatabasePath((String) args[0]);
+        }).when(mMockContext).getDatabasePath(anyString());
 
         when(mMockResources.getBoolean(R.bool.profile_supported_gatt)).thenReturn(true);
         when(mMockResources.getBoolean(R.bool.profile_supported_pbap)).thenReturn(true);
@@ -128,6 +160,9 @@
         mAdapterService.registerCallback(mIBluetoothCallback);
 
         Config.init(mMockContext);
+
+        mAdapterConfig = TestUtils.readAdapterConfig();
+        Assert.assertNotNull(mAdapterConfig);
     }
 
     @After
@@ -183,6 +218,8 @@
         verifyStateChange(BluetoothAdapter.STATE_TURNING_ON, BluetoothAdapter.STATE_ON,
                 invocationNumber + 1, CONTEXT_SWITCH_MS);
 
+        verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times(2 * invocationNumber + 2))
+                .sendBroadcast(any(), eq(android.Manifest.permission.BLUETOOTH));
         final int scanMode = mAdapterService.getScanMode();
         Assert.assertTrue(scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE
                 || scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
@@ -221,7 +258,7 @@
         mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_OFF);
 
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF,
-                invocationNumber + 1, CONTEXT_SWITCH_MS);
+                invocationNumber + 1, NATIVE_DISABLE_MS);
 
         Assert.assertFalse(mAdapterService.isEnabled());
     }
@@ -297,7 +334,7 @@
                 .times(2)).startService(any());
 
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
-                CONTEXT_SWITCH_MS);
+                NATIVE_DISABLE_MS);
 
         Assert.assertFalse(mAdapterService.isEnabled());
     }
@@ -333,7 +370,7 @@
         verify(mMockContext, timeout(ONE_SECOND_MS).times(6)).startService(any());
 
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
-                AdapterState.BLE_STOP_TIMEOUT_DELAY + CONTEXT_SWITCH_MS);
+                AdapterState.BLE_STOP_TIMEOUT_DELAY + NATIVE_DISABLE_MS);
 
         Assert.assertFalse(mAdapterService.isEnabled());
     }
@@ -409,7 +446,7 @@
         mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_OFF);
 
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
-                AdapterState.BLE_STOP_TIMEOUT_DELAY + CONTEXT_SWITCH_MS);
+                AdapterState.BLE_STOP_TIMEOUT_DELAY + NATIVE_DISABLE_MS);
 
         Assert.assertFalse(mAdapterService.isEnabled());
     }
@@ -456,11 +493,225 @@
         mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_OFF);
 
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
-                CONTEXT_SWITCH_MS);
+                NATIVE_DISABLE_MS);
 
         Assert.assertFalse(mAdapterService.isEnabled());
 
         // Restore earlier setting
         SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, snoopSetting);
     }
+
+
+    /**
+     * Test: Obfuscate a null Bluetooth
+     * Check if returned value from {@link AdapterService#obfuscateAddress(BluetoothDevice)} is
+     * an empty array when device address is null
+     */
+    @Test
+    public void testObfuscateBluetoothAddress_NullAddress() {
+        Assert.assertArrayEquals(mAdapterService.obfuscateAddress(null), new byte[0]);
+    }
+
+    /**
+     * Test: Obfuscate Bluetooth address when Bluetooth is disabled
+     * Check whether the returned value meets expectation
+     */
+    @Test
+    public void testObfuscateBluetoothAddress_BluetoothDisabled() {
+        Assert.assertFalse(mAdapterService.isEnabled());
+        byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
+        Assert.assertNotNull(metricsSalt);
+        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+        byte[] obfuscatedAddress = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress.length > 0);
+        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress));
+        Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device), obfuscatedAddress);
+    }
+
+    /**
+     * Test: Obfuscate Bluetooth address when Bluetooth is enabled
+     * Check whether the returned value meets expectation
+     */
+    @Test
+    public void testObfuscateBluetoothAddress_BluetoothEnabled() {
+        Assert.assertFalse(mAdapterService.isEnabled());
+        doEnable(0, false);
+        Assert.assertTrue(mAdapterService.isEnabled());
+        byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
+        Assert.assertNotNull(metricsSalt);
+        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+        byte[] obfuscatedAddress = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress.length > 0);
+        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress));
+        Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device), obfuscatedAddress);
+    }
+
+    /**
+     * Test: Check if obfuscated Bluetooth address stays the same after toggling Bluetooth
+     */
+    @Test
+    public void testObfuscateBluetoothAddress_PersistentBetweenToggle() {
+        Assert.assertFalse(mAdapterService.isEnabled());
+        byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
+        Assert.assertNotNull(metricsSalt);
+        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+        byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress1.length > 0);
+        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
+        Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device),
+                obfuscatedAddress1);
+        // Enable
+        doEnable(0, false);
+        Assert.assertTrue(mAdapterService.isEnabled());
+        byte[] obfuscatedAddress3 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress3.length > 0);
+        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3));
+        Assert.assertArrayEquals(obfuscatedAddress3,
+                obfuscatedAddress1);
+        // Disable
+        doDisable(0, false);
+        Assert.assertFalse(mAdapterService.isEnabled());
+        byte[] obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress4.length > 0);
+        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4));
+        Assert.assertArrayEquals(obfuscatedAddress4,
+                obfuscatedAddress1);
+    }
+
+    /**
+     * Test: Check if obfuscated Bluetooth address stays the same after re-initializing
+     *       {@link AdapterService}
+     */
+    @Test
+    public void testObfuscateBluetoothAddress_PersistentBetweenAdapterServiceInitialization() throws
+            PackageManager.NameNotFoundException {
+        byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
+        Assert.assertNotNull(metricsSalt);
+        Assert.assertFalse(mAdapterService.isEnabled());
+        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+        byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress1.length > 0);
+        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
+        Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device),
+                obfuscatedAddress1);
+        tearDown();
+        setUp();
+        Assert.assertFalse(mAdapterService.isEnabled());
+        byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress2.length > 0);
+        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
+        Assert.assertArrayEquals(obfuscatedAddress2,
+                obfuscatedAddress1);
+    }
+
+    /**
+     * Test: Verify that obfuscated Bluetooth address changes after factory reset
+     *
+     * There are 4 types of factory reset that we are talking about:
+     * 1. Factory reset all user data from Settings -> Will restart phone
+     * 2. Factory reset WiFi and Bluetooth from Settings -> Will only restart WiFi and BT
+     * 3. Call BluetoothAdapter.factoryReset() -> Will disable Bluetooth and reset config in
+     * memory and disk
+     * 4. Call AdapterService.factoryReset() -> Will only reset config in memory
+     *
+     * We can only use No. 4 here
+     */
+    @Ignore("AdapterService.factoryReset() does not reload config into memory and hence old salt"
+            + " is still used until next time Bluetooth library is initialized. However Bluetooth"
+            + " cannot be used until Bluetooth process restart any way. Thus it is almost"
+            + " guaranteed that user has to re-enable Bluetooth and hence re-generate new salt"
+            + " after factory reset")
+    @Test
+    public void testObfuscateBluetoothAddress_FactoryReset() {
+        Assert.assertFalse(mAdapterService.isEnabled());
+        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+        byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress1.length > 0);
+        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
+        mAdapterService.factoryReset();
+        byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress2.length > 0);
+        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
+        Assert.assertFalse(Arrays.equals(obfuscatedAddress2,
+                obfuscatedAddress1));
+        doEnable(0, false);
+        byte[] obfuscatedAddress3 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress3.length > 0);
+        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3));
+        Assert.assertArrayEquals(obfuscatedAddress3,
+                obfuscatedAddress2);
+        mAdapterService.factoryReset();
+        byte[] obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress4.length > 0);
+        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4));
+        Assert.assertFalse(Arrays.equals(obfuscatedAddress4,
+                obfuscatedAddress3));
+    }
+
+    /**
+     * Test: Verify that obfuscated Bluetooth address changes after factory reset and reloading
+     *       native layer
+     */
+    @Test
+    public void testObfuscateBluetoothAddress_FactoryResetAndReloadNativeLayer() throws
+            PackageManager.NameNotFoundException {
+        byte[] metricsSalt1 = getMetricsSalt(mAdapterConfig);
+        Assert.assertNotNull(metricsSalt1);
+        Assert.assertFalse(mAdapterService.isEnabled());
+        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+        byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress1.length > 0);
+        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
+        Assert.assertArrayEquals(obfuscateInJava(metricsSalt1, device),
+                obfuscatedAddress1);
+        mAdapterService.factoryReset();
+        tearDown();
+        setUp();
+        // Cannot verify metrics salt since it is not written to disk until native cleanup
+        byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress2.length > 0);
+        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
+        Assert.assertFalse(Arrays.equals(obfuscatedAddress2,
+                obfuscatedAddress1));
+    }
+
+    private static byte[] getMetricsSalt(HashMap<String, HashMap<String, String>> adapterConfig) {
+        HashMap<String, String> metricsSection = adapterConfig.get("Metrics");
+        if (metricsSection == null) {
+            Log.e(TAG, "Metrics section is null: " + adapterConfig.toString());
+            return null;
+        }
+        String saltString = metricsSection.get("Salt256Bit");
+        if (saltString == null) {
+            Log.e(TAG, "Salt256Bit is null: " + metricsSection.toString());
+            return null;
+        }
+        byte[] metricsSalt = HexEncoding.decode(saltString, false /* allowSingleChar */);
+        if (metricsSalt.length != 32) {
+            Log.e(TAG, "Salt length is not 32 bit, but is " + metricsSalt.length);
+            return null;
+        }
+        return metricsSalt;
+    }
+
+    private static byte[] obfuscateInJava(byte[] key, BluetoothDevice device) {
+        String algorithm = "HmacSHA256";
+        try {
+            Mac hmac256 = Mac.getInstance(algorithm);
+            hmac256.init(new SecretKeySpec(key, algorithm));
+            return hmac256.doFinal(Utils.getByteAddress(device));
+        } catch (NoSuchAlgorithmException | IllegalStateException | InvalidKeyException exp) {
+            exp.printStackTrace();
+            return null;
+        }
+    }
+
+    private static boolean isByteArrayAllZero(byte[] byteArray) {
+        for (byte i : byteArray) {
+            if (i != 0) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java b/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java
index 29585ff..b9c8953 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java
@@ -23,9 +23,10 @@
 import android.os.HandlerThread;
 import android.os.ParcelUuid;
 import android.os.UserHandle;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.TestUtils;
 
diff --git a/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java b/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
index b15c7b4..cc4e8b7 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
@@ -15,8 +15,8 @@
  */
 package com.android.bluetooth.btservice;
 
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.BluetoothMetricsProto.BluetoothLog;
 import com.android.bluetooth.BluetoothMetricsProto.ProfileConnectionStats;
diff --git a/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java b/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
index 510b9af..dafdc5a 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
@@ -24,12 +24,12 @@
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
-import android.content.BroadcastReceiver;
 import android.content.Intent;
 import android.os.HandlerThread;
 import android.os.ParcelUuid;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.a2dp.A2dpService;
@@ -234,21 +234,20 @@
         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
 
-        // This should not have any effect
+        // Verify that the priority of previous active device won't be changed while active device
+        // set to null
         verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setPriority(
                 bondedDevices[1], BluetoothProfile.PRIORITY_ON);
+        verify(mHeadsetService).setPriority(bondedDevices[1],
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+        verify(mHeadsetService, never()).setPriority(bondedDevices[1],
+                BluetoothProfile.PRIORITY_OFF);
 
         // Make the current active device fail to connect
-        when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
-                BluetoothProfile.STATE_DISCONNECTED);
         when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
                 BluetoothProfile.STATE_DISCONNECTED);
-        intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[1]);
-        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTING);
-        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
-        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+        updateProfileConnectionStateHelper(bondedDevices[1], BluetoothProfile.HEADSET,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
 
         // This device should be set to ON
         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).setPriority(
@@ -259,8 +258,8 @@
     }
 
     /**
-     * Test that we will try to re-connect to a profile on a device if an attempt failed previously.
-     * This is to add robustness to the connection mechanism
+     * Test that we will try to re-connect to a profile on a device if other profile(s) are
+     * connected. This is to add robustness to the connection mechanism
      */
     @Test
     public void testReconnectOnPartialConnect() {
@@ -290,12 +289,8 @@
 
         // We send a connection successful for one profile since the re-connect *only* works if we
         // have already connected successfully over one of the profiles
-        Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]);
-        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
-        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
-        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+        updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
 
         // Check that we get a call to A2DP connect
         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
@@ -303,6 +298,81 @@
     }
 
     /**
+     * Test that we will try to re-connect to a profile on a device next time if a previous attempt
+     * failed partially. This will make sure the connection mechanism still works at next try while
+     * the previous attempt is some profiles connected on a device but some not.
+     */
+    @Test
+    public void testReconnectOnPartialConnect_PreviousPartialFail() {
+        // Return a list of bonded devices (just one)
+        BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
+        bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
+        when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
+
+        // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
+        // auto-connectable.
+        when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+        when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+
+        when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
+
+        // We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP
+        // To enable that we need to make sure that HeadsetService returns the device among a list
+        // of connected devices
+        ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
+        hsConnectedDevices.add(bondedDevices[0]);
+        when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
+        // Also the A2DP should say that its not connected for same device
+        when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+
+        // We send a connection success event for one profile since the re-connect *only* works if
+        // we have already connected successfully over one of the profiles
+        updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
+
+        // Check that we get a call to A2DP reconnect
+        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
+                bondedDevices[0]);
+
+        // We send a connection failure event for the attempted profile, and keep the connected
+        // profile connected.
+        updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.A2DP,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
+
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+
+        // Verify no one changes the priority of the failed profile
+        verify(mA2dpService, never()).setPriority(eq(bondedDevices[0]), anyInt());
+
+        // Send a connection success event for one profile again without disconnecting all profiles
+        updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
+
+        // Check that we won't get a call to A2DP reconnect again before all profiles disconnected
+        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
+                bondedDevices[0]);
+
+        // Send a disconnection event for all connected profiles
+        hsConnectedDevices.remove(bondedDevices[0]);
+        updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED);
+
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+
+        // Send a connection success event for one profile again to trigger re-connect
+        hsConnectedDevices.add(bondedDevices[0]);
+        updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
+
+        // Check that we get a call to A2DP connect again
+        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).times(2)).connect(
+                bondedDevices[0]);
+    }
+
+    /**
      * Test that a second device will auto-connect if there is already one connected device.
      *
      * Even though we currently only set one device to be auto connect. The consumer of the auto
@@ -352,21 +422,13 @@
 
         // We send a connection successful for one profile since the re-connect *only* works if we
         // have already connected successfully over one of the profiles
-        Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, a2dpNotConnectedDevice1);
-        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
-        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
-        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+        updateProfileConnectionStateHelper(a2dpNotConnectedDevice1, BluetoothProfile.HEADSET,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
 
         // We send a connection successful for one profile since the re-connect *only* works if we
         // have already connected successfully over one of the profiles
-        intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, a2dpNotConnectedDevice2);
-        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
-        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
-        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+        updateProfileConnectionStateHelper(a2dpNotConnectedDevice2, BluetoothProfile.HEADSET,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
 
         // Check that we get a call to A2DP connect
         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
@@ -453,17 +515,12 @@
         when(mA2dpService.getConnectionState(testDevices[3])).thenReturn(
                 BluetoothProfile.STATE_DISCONNECTED);
 
-        // Get the broadcast receiver to inject events
-        BroadcastReceiver injector = mPhonePolicy.getBroadcastReceiver();
 
         // Generate connection state changed for HFP for testDevices[1] and trigger
         // auto-connect for A2DP.
-        Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, testDevices[1]);
-        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
-        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
-        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        injector.onReceive(null /* context */, intent);
+        updateProfileConnectionStateHelper(testDevices[1], BluetoothProfile.HEADSET,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
+
         // Check that we get a call to A2DP connect
         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
                 eq(testDevices[1]));
@@ -494,12 +551,9 @@
 
         // Generate connection state changed for A2DP for testDevices[2] and trigger
         // auto-connect for HFP.
-        intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, testDevices[2]);
-        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
-        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
-        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        injector.onReceive(null /* context */, intent);
+        updateProfileConnectionStateHelper(testDevices[2], BluetoothProfile.A2DP,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
+
         // Check that we get a call to HFP connect
         verify(mHeadsetService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
                 eq(testDevices[2]));
@@ -665,17 +719,11 @@
                 BluetoothProfile.STATE_DISCONNECTED);
         when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
                 BluetoothProfile.STATE_DISCONNECTED);
-        when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
-                BluetoothProfile.STATE_CONNECTED);
 
         // We send a connection successful for one profile since the re-connect *only* works if we
         // have already connected successfully over one of the profiles
-        Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[1]);
-        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
-        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
-        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+        updateProfileConnectionStateHelper(bondedDevices[1], BluetoothProfile.HEADSET,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
 
         // Check that we don't get any calls to reconnect
         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
@@ -708,4 +756,27 @@
                 eq(BluetoothProfile.PRIORITY_ON));
         verify(mA2dpService, never()).setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
     }
+
+    private void updateProfileConnectionStateHelper(BluetoothDevice device, int profileId,
+            int nextState, int prevState) {
+        Intent intent;
+        switch (profileId) {
+            case BluetoothProfile.A2DP:
+                when(mA2dpService.getConnectionState(device)).thenReturn(nextState);
+                intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+                break;
+            case BluetoothProfile.HEADSET:
+                when(mHeadsetService.getConnectionState(device)).thenReturn(nextState);
+                intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+                break;
+            default:
+                intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
+                break;
+        }
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, nextState);
+        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+    }
 }
diff --git a/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
index ca20fc3..999ccd1 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
@@ -22,10 +22,11 @@
 import android.bluetooth.BluetoothAdapter;
 import android.content.Intent;
 import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.TestUtils;
 
diff --git a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
index 35215ef..e22f485 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
@@ -11,9 +11,10 @@
 import android.os.HandlerThread;
 import android.os.Message;
 import android.os.TestLooperManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.hfp.HeadsetHalConstants;
@@ -274,6 +275,7 @@
         // Verify ACTION_ACL_DISCONNECTED and BATTERY_LEVEL_CHANGED intent are sent
         verify(mAdapterService, times(3)).sendBroadcast(mIntentArgument.capture(),
                 mStringArgument.capture());
+        verify(mAdapterService).obfuscateAddress(mDevice1);
         verifyBatteryLevelChangedIntent(mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
                 mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
         Assert.assertEquals(AdapterService.BLUETOOTH_PERM,
@@ -336,7 +338,7 @@
                 BluetoothAssignedNumbers.PLANTRONICS, BluetoothHeadset.AT_CMD_TYPE_SET,
                 getXEventArray(3, 8), mDevice1));
         verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
-        verifyBatteryLevelChangedIntent(mDevice1, 37, mIntentArgument);
+        verifyBatteryLevelChangedIntent(mDevice1, 42, mIntentArgument);
         Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
     }
 
@@ -364,8 +366,11 @@
 
     @Test
     public void testGetBatteryLevelFromXEventVsc() {
-        Assert.assertEquals(37, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 8)));
-        Assert.assertEquals(100, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(1, 1)));
+        Assert.assertEquals(42, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 8)));
+        Assert.assertEquals(100,
+                RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(10, 11)));
+        Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+                RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(1, 1)));
         Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
                 RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 1)));
         Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
@@ -436,7 +441,8 @@
         Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
         Assert.assertEquals(batteryLevel,
                 intent.getIntExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, -15));
-        Assert.assertEquals(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, intent.getFlags());
+        Assert.assertEquals(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, intent.getFlags());
     }
 
     private static Intent getHeadsetConnectionStateChangedIntent(BluetoothDevice device,
diff --git a/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java b/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java
new file mode 100644
index 0000000..0f7864a
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.Intent;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.hfp.HeadsetService;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class SilenceDeviceManagerTest {
+    private BluetoothAdapter mAdapter;
+    private Context mContext;
+    private BluetoothDevice mTestDevice;
+    private SilenceDeviceManager mSilenceDeviceManager;
+    private HandlerThread mHandlerThread;
+    private Looper mLooper;
+    private static final String TEST_BT_ADDR = "11:22:33:44:55:66";
+    private int mVerifyCount = 0;
+
+    @Mock private AdapterService mAdapterService;
+    @Mock private ServiceFactory mServiceFactory;
+    @Mock private A2dpService mA2dpService;
+    @Mock private HeadsetService mHeadsetService;
+
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+
+        // Set up mocks and test assets
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        when(mServiceFactory.getA2dpService()).thenReturn(mA2dpService);
+        when(mServiceFactory.getHeadsetService()).thenReturn(mHeadsetService);
+
+        // Get devices for testing
+        mTestDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_BT_ADDR);
+
+        mHandlerThread = new HandlerThread("SilenceManagerTestHandlerThread");
+        mHandlerThread.start();
+        mLooper = mHandlerThread.getLooper();
+        mSilenceDeviceManager = new SilenceDeviceManager(mAdapterService, mServiceFactory,
+                mLooper);
+        mSilenceDeviceManager.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSilenceDeviceManager.cleanup();
+        mHandlerThread.quit();
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testSetGetDeviceSilence() {
+        testSetGetDeviceSilenceConnectedCase(false, true);
+        testSetGetDeviceSilenceConnectedCase(false, false);
+        testSetGetDeviceSilenceConnectedCase(true, true);
+        testSetGetDeviceSilenceConnectedCase(true, false);
+
+        testSetGetDeviceSilenceDisconnectedCase(false);
+        testSetGetDeviceSilenceDisconnectedCase(true);
+    }
+
+    void testSetGetDeviceSilenceConnectedCase(boolean wasSilenced, boolean enableSilence) {
+        ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+        doReturn(true).when(mA2dpService).setSilenceMode(mTestDevice, enableSilence);
+        doReturn(true).when(mHeadsetService).setSilenceMode(mTestDevice, enableSilence);
+
+        // Send A2DP/HFP connected intent
+        a2dpConnected(mTestDevice);
+        headsetConnected(mTestDevice);
+
+        // Set pre-state for mSilenceDeviceManager
+        if (wasSilenced) {
+            Assert.assertTrue(mSilenceDeviceManager.setSilenceMode(mTestDevice, true));
+            TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+            verify(mAdapterService, times(++mVerifyCount)).sendBroadcastAsUser(
+                    intentArgument.capture(), eq(UserHandle.ALL),
+                    eq(AdapterService.BLUETOOTH_PERM));
+        }
+
+        // Set silence state and check whether state changed successfully
+        Assert.assertTrue(mSilenceDeviceManager.setSilenceMode(mTestDevice, enableSilence));
+        TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+        Assert.assertEquals(enableSilence, mSilenceDeviceManager.getSilenceMode(mTestDevice));
+
+        // Check for silence state changed intent
+        if (wasSilenced != enableSilence) {
+            verify(mAdapterService, times(++mVerifyCount)).sendBroadcastAsUser(
+                    intentArgument.capture(), eq(UserHandle.ALL),
+                    eq(AdapterService.BLUETOOTH_PERM));
+            verifySilenceStateIntent(intentArgument.getValue());
+        }
+
+        // Remove test devices
+        a2dpDisconnected(mTestDevice);
+        headsetDisconnected(mTestDevice);
+
+        Assert.assertFalse(mSilenceDeviceManager.getSilenceMode(mTestDevice));
+        if (enableSilence) {
+            // If the silence mode is enabled, it should be automatically disabled
+            // after device is disconnected.
+            verify(mAdapterService, times(++mVerifyCount)).sendBroadcastAsUser(
+                    intentArgument.capture(), eq(UserHandle.ALL),
+                    eq(AdapterService.BLUETOOTH_PERM));
+        }
+    }
+
+    void testSetGetDeviceSilenceDisconnectedCase(boolean enableSilence) {
+        ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+        // Set silence mode and it should stay disabled
+        Assert.assertTrue(mSilenceDeviceManager.setSilenceMode(mTestDevice, enableSilence));
+        TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+        Assert.assertFalse(mSilenceDeviceManager.getSilenceMode(mTestDevice));
+
+        // Should be no intent been broadcasted
+        verify(mAdapterService, times(mVerifyCount)).sendBroadcastAsUser(
+                intentArgument.capture(), eq(UserHandle.ALL),
+                eq(AdapterService.BLUETOOTH_PERM));
+    }
+
+    void verifySilenceStateIntent(Intent intent) {
+        Assert.assertEquals(BluetoothDevice.ACTION_SILENCE_MODE_CHANGED, intent.getAction());
+        Assert.assertEquals(mTestDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
+    }
+
+    /**
+     * Helper to indicate A2dp connected for a device.
+     */
+    private void a2dpConnected(BluetoothDevice device) {
+        Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
+        mSilenceDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+        TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+    }
+
+    /**
+     * Helper to indicate A2dp disconnected for a device.
+     */
+    private void a2dpDisconnected(BluetoothDevice device) {
+        Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
+        mSilenceDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+        TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+    }
+
+    /**
+     * Helper to indicate Headset connected for a device.
+     */
+    private void headsetConnected(BluetoothDevice device) {
+        Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
+        mSilenceDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+        TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+    }
+
+    /**
+     * Helper to indicate Headset disconnected for a device.
+     */
+    private void headsetDisconnected(BluetoothDevice device) {
+        Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
+        mSilenceDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+        TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+    }
+}
+
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
new file mode 100644
index 0000000..2cb3630
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
@@ -0,0 +1,698 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice.storage;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import androidx.room.Room;
+import androidx.room.testing.MigrationTestHelper;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.util.List;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public final class DatabaseManagerTest {
+
+    @Mock private AdapterService mAdapterService;
+
+    private MetadataDatabase mDatabase;
+    private DatabaseManager mDatabaseManager;
+    private BluetoothDevice mTestDevice;
+    private BluetoothDevice mTestDevice2;
+
+    private static final String LOCAL_STORAGE = "LocalStorage";
+    private static final String TEST_BT_ADDR = "11:22:33:44:55:66";
+    private static final String OTHER_BT_ADDR1 = "11:11:11:11:11:11";
+    private static final String OTHER_BT_ADDR2 = "22:22:22:22:22:22";
+    private static final String DB_NAME = "test_db";
+    private static final int A2DP_SUPPORT_OP_CODEC_TEST = 0;
+    private static final int A2DP_ENALBED_OP_CODEC_TEST = 1;
+    private static final int MAX_META_ID = 16;
+    private static final byte[] TEST_BYTE_ARRAY = "TEST_VALUE".getBytes();
+
+    @Rule
+    public MigrationTestHelper testHelper = new MigrationTestHelper(
+            InstrumentationRegistry.getInstrumentation(),
+            MetadataDatabase.class.getCanonicalName(),
+            new FrameworkSQLiteOpenHelperFactory());
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+
+        mTestDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_BT_ADDR);
+
+        // Create a memory database for DatabaseManager instead of use a real database.
+        mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
+                MetadataDatabase.class).build();
+
+        when(mAdapterService.getPackageManager()).thenReturn(
+                InstrumentationRegistry.getTargetContext().getPackageManager());
+        mDatabaseManager = new DatabaseManager(mAdapterService);
+
+        BluetoothDevice[] bondedDevices = {mTestDevice};
+        doReturn(bondedDevices).when(mAdapterService).getBondedDevices();
+        doNothing().when(mAdapterService).metadataChanged(
+                anyString(), anyInt(), any(byte[].class));
+
+        restartDatabaseManagerHelper();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        TestUtils.clearAdapterService(mAdapterService);
+        mDatabase.deleteAll();
+        mDatabaseManager.cleanup();
+    }
+
+    @Test
+    public void testMetadataDefault() {
+        Metadata data = new Metadata(TEST_BT_ADDR);
+        mDatabase.insert(data);
+        restartDatabaseManagerHelper();
+
+        for (int id = 0; id < BluetoothProfile.MAX_PROFILE_ID; id++) {
+            Assert.assertEquals(BluetoothProfile.PRIORITY_UNDEFINED,
+                    mDatabaseManager.getProfilePriority(mTestDevice, id));
+        }
+
+        Assert.assertEquals(BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN,
+                mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice));
+
+        Assert.assertEquals(BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+                    mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice));
+
+        for (int id = 0; id < MAX_META_ID; id++) {
+            Assert.assertNull(mDatabaseManager.getCustomMeta(mTestDevice, id));
+        }
+    }
+
+    @Test
+    public void testSetGetProfilePriority() {
+        int badPriority = -100;
+
+        // Cases of device not in database
+        testSetGetProfilePriorityCase(false, BluetoothProfile.PRIORITY_UNDEFINED,
+                BluetoothProfile.PRIORITY_UNDEFINED, true);
+        testSetGetProfilePriorityCase(false, BluetoothProfile.PRIORITY_OFF,
+                BluetoothProfile.PRIORITY_OFF, true);
+        testSetGetProfilePriorityCase(false, BluetoothProfile.PRIORITY_ON,
+                BluetoothProfile.PRIORITY_ON, true);
+        testSetGetProfilePriorityCase(false, BluetoothProfile.PRIORITY_AUTO_CONNECT,
+                BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+        testSetGetProfilePriorityCase(false, badPriority,
+                BluetoothProfile.PRIORITY_UNDEFINED, false);
+
+        // Cases of device already in database
+        testSetGetProfilePriorityCase(true, BluetoothProfile.PRIORITY_UNDEFINED,
+                BluetoothProfile.PRIORITY_UNDEFINED, true);
+        testSetGetProfilePriorityCase(true, BluetoothProfile.PRIORITY_OFF,
+                BluetoothProfile.PRIORITY_OFF, true);
+        testSetGetProfilePriorityCase(true, BluetoothProfile.PRIORITY_ON,
+                BluetoothProfile.PRIORITY_ON, true);
+        testSetGetProfilePriorityCase(true, BluetoothProfile.PRIORITY_AUTO_CONNECT,
+                BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+        testSetGetProfilePriorityCase(true, badPriority,
+                BluetoothProfile.PRIORITY_UNDEFINED, false);
+    }
+
+    @Test
+    public void testSetGetA2dpSupportsOptionalCodecs() {
+        int badValue = -100;
+
+        // Cases of device not in database
+        testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, false,
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN,
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+        testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, false,
+                BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED,
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+        testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, false,
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED,
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+        testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, false,
+                badValue, BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+
+        // Cases of device already in database
+        testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, true,
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN,
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+        testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, true,
+                BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED,
+                BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
+        testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, true,
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED,
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
+        testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, true,
+                badValue, BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+    }
+
+    @Test
+    public void testSetGetA2dpOptionalCodecsEnabled() {
+        int badValue = -100;
+
+        // Cases of device not in database
+        testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, false,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+        testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, false,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+        testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, false,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+        testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, false,
+                badValue, BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+
+        // Cases of device already in database
+        testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, true,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+        testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, true,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED);
+        testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, true,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
+        testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, true,
+                badValue, BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+    }
+
+    @Test
+    public void testRemoveUnusedMetadata_WithSingleBondedDevice() {
+        // Insert two devices to database and cache, only mTestDevice is
+        // in the bonded list
+        Metadata otherData = new Metadata(OTHER_BT_ADDR1);
+        // Add metadata for otherDevice
+        otherData.setCustomizedMeta(0, TEST_BYTE_ARRAY);
+        mDatabaseManager.mMetadataCache.put(OTHER_BT_ADDR1, otherData);
+        mDatabase.insert(otherData);
+
+        Metadata data = new Metadata(TEST_BT_ADDR);
+        mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data);
+        mDatabase.insert(data);
+
+        mDatabaseManager.removeUnusedMetadata();
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+        // Check removed device report metadata changed to null
+        verify(mAdapterService).metadataChanged(OTHER_BT_ADDR1, 0, null);
+
+        List<Metadata> list = mDatabase.load();
+
+        // Check number of metadata in the database
+        Assert.assertEquals(1, list.size());
+
+        // Check whether the device is in database
+        Metadata checkData = list.get(0);
+        Assert.assertEquals(TEST_BT_ADDR, checkData.getAddress());
+
+        mDatabase.deleteAll();
+        // Wait for clear database
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        mDatabaseManager.mMetadataCache.clear();
+    }
+
+    @Test
+    public void testRemoveUnusedMetadata_WithMultiBondedDevices() {
+        // Insert three devices to database and cache, otherDevice1 and otherDevice2
+        // are in the bonded list
+
+        // Add metadata for TEST_BT_ADDR
+        Metadata testData = new Metadata(TEST_BT_ADDR);
+        testData.setCustomizedMeta(0, TEST_BYTE_ARRAY);
+        mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, testData);
+        mDatabase.insert(testData);
+
+        // Add metadata for OTHER_BT_ADDR1
+        Metadata otherData1 = new Metadata(OTHER_BT_ADDR1);
+        otherData1.setCustomizedMeta(0, TEST_BYTE_ARRAY);
+        mDatabaseManager.mMetadataCache.put(OTHER_BT_ADDR1, otherData1);
+        mDatabase.insert(otherData1);
+
+        // Add metadata for OTHER_BT_ADDR2
+        Metadata otherData2 = new Metadata(OTHER_BT_ADDR2);
+        otherData2.setCustomizedMeta(0, TEST_BYTE_ARRAY);
+        mDatabaseManager.mMetadataCache.put(OTHER_BT_ADDR2, otherData2);
+        mDatabase.insert(otherData2);
+
+        // Add OTHER_BT_ADDR1 OTHER_BT_ADDR2 to bonded devices
+        BluetoothDevice otherDevice1 = BluetoothAdapter.getDefaultAdapter()
+                .getRemoteDevice(OTHER_BT_ADDR1);
+        BluetoothDevice otherDevice2 = BluetoothAdapter.getDefaultAdapter()
+                .getRemoteDevice(OTHER_BT_ADDR2);
+        BluetoothDevice[] bondedDevices = {otherDevice1, otherDevice2};
+        doReturn(bondedDevices).when(mAdapterService).getBondedDevices();
+
+        mDatabaseManager.removeUnusedMetadata();
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+        // Check TEST_BT_ADDR report metadata changed to null
+        verify(mAdapterService).metadataChanged(TEST_BT_ADDR, 0, null);
+
+        // Check number of metadata in the database
+        List<Metadata> list = mDatabase.load();
+        // OTHER_BT_ADDR1 and OTHER_BT_ADDR2 should still in database
+        Assert.assertEquals(2, list.size());
+
+        // Check whether the devices are in the database
+        Metadata checkData1 = list.get(0);
+        Assert.assertEquals(OTHER_BT_ADDR1, checkData1.getAddress());
+        Metadata checkData2 = list.get(1);
+        Assert.assertEquals(OTHER_BT_ADDR2, checkData2.getAddress());
+
+        mDatabase.deleteAll();
+        // Wait for clear database
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        mDatabaseManager.mMetadataCache.clear();
+
+    }
+
+    @Test
+    public void testSetGetCustomMeta() {
+        int badKey = 100;
+        byte[] value = "input value".getBytes();
+
+        // Device is not in database
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MANUFACTURER_NAME,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MODEL_NAME,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_SOFTWARE_VERSION,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_HARDWARE_VERSION,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_COMPANION_APP,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MAIN_ICON,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_CASE_ICON,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI,
+                value, true);
+        testSetGetCustomMetaCase(false, badKey, value, false);
+
+        // Device is in database
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MANUFACTURER_NAME,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MODEL_NAME,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_SOFTWARE_VERSION,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_HARDWARE_VERSION,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_COMPANION_APP,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MAIN_ICON,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_CASE_ICON,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI,
+                value, true);
+    }
+
+    @Test
+    public void testDatabaseMigration_100_101() throws IOException {
+        // Create a database with version 100
+        SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 100);
+        Cursor cursor = db.query("SELECT * FROM metadata");
+
+        // pbap_client_priority should not in version 100
+        assertHasColumn(cursor, "pbap_client_priority", false);
+
+        // Migrate database from 100 to 101
+        db.close();
+        db = testHelper.runMigrationsAndValidate(DB_NAME, 101, true,
+                MetadataDatabase.MIGRATION_100_101);
+        cursor = db.query("SELECT * FROM metadata");
+
+        // Check whether pbap_client_priority exists in version 101
+        assertHasColumn(cursor, "pbap_client_priority", true);
+    }
+
+    @Test
+    public void testDatabaseMigration_101_102() throws IOException {
+        String testString = "TEST STRING";
+
+        // Create a database with version 101
+        SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 101);
+        Cursor cursor = db.query("SELECT * FROM metadata");
+
+        // insert a device to the database
+        ContentValues device = new ContentValues();
+        device.put("address", TEST_BT_ADDR);
+        device.put("migrated", false);
+        device.put("a2dpSupportsOptionalCodecs", -1);
+        device.put("a2dpOptionalCodecsEnabled", -1);
+        device.put("a2dp_priority", -1);
+        device.put("a2dp_sink_priority", -1);
+        device.put("hfp_priority", -1);
+        device.put("hfp_client_priority", -1);
+        device.put("hid_host_priority", -1);
+        device.put("pan_priority", -1);
+        device.put("pbap_priority", -1);
+        device.put("pbap_client_priority", -1);
+        device.put("map_priority", -1);
+        device.put("sap_priority", -1);
+        device.put("hearing_aid_priority", -1);
+        device.put("map_client_priority", -1);
+        device.put("manufacturer_name", testString);
+        device.put("model_name", testString);
+        device.put("software_version", testString);
+        device.put("hardware_version", testString);
+        device.put("companion_app", testString);
+        device.put("main_icon", testString);
+        device.put("is_unthethered_headset", testString);
+        device.put("unthethered_left_icon", testString);
+        device.put("unthethered_right_icon", testString);
+        device.put("unthethered_case_icon", testString);
+        device.put("unthethered_left_battery", testString);
+        device.put("unthethered_right_battery", testString);
+        device.put("unthethered_case_battery", testString);
+        device.put("unthethered_left_charging", testString);
+        device.put("unthethered_right_charging", testString);
+        device.put("unthethered_case_charging", testString);
+        Assert.assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
+                CoreMatchers.not(-1));
+
+        // Check the metadata names on version 101
+        assertHasColumn(cursor, "is_unthethered_headset", true);
+        assertHasColumn(cursor, "unthethered_left_icon", true);
+        assertHasColumn(cursor, "unthethered_right_icon", true);
+        assertHasColumn(cursor, "unthethered_case_icon", true);
+        assertHasColumn(cursor, "unthethered_left_battery", true);
+        assertHasColumn(cursor, "unthethered_right_battery", true);
+        assertHasColumn(cursor, "unthethered_case_battery", true);
+        assertHasColumn(cursor, "unthethered_left_charging", true);
+        assertHasColumn(cursor, "unthethered_right_charging", true);
+        assertHasColumn(cursor, "unthethered_case_charging", true);
+
+        // Migrate database from 101 to 102
+        db.close();
+        db = testHelper.runMigrationsAndValidate(DB_NAME, 102, true,
+                MetadataDatabase.MIGRATION_101_102);
+        cursor = db.query("SELECT * FROM metadata");
+
+        // metadata names should be changed on version 102
+        assertHasColumn(cursor, "is_unthethered_headset", false);
+        assertHasColumn(cursor, "unthethered_left_icon", false);
+        assertHasColumn(cursor, "unthethered_right_icon", false);
+        assertHasColumn(cursor, "unthethered_case_icon", false);
+        assertHasColumn(cursor, "unthethered_left_battery", false);
+        assertHasColumn(cursor, "unthethered_right_battery", false);
+        assertHasColumn(cursor, "unthethered_case_battery", false);
+        assertHasColumn(cursor, "unthethered_left_charging", false);
+        assertHasColumn(cursor, "unthethered_right_charging", false);
+        assertHasColumn(cursor, "unthethered_case_charging", false);
+
+        assertHasColumn(cursor, "is_untethered_headset", true);
+        assertHasColumn(cursor, "untethered_left_icon", true);
+        assertHasColumn(cursor, "untethered_right_icon", true);
+        assertHasColumn(cursor, "untethered_case_icon", true);
+        assertHasColumn(cursor, "untethered_left_battery", true);
+        assertHasColumn(cursor, "untethered_right_battery", true);
+        assertHasColumn(cursor, "untethered_case_battery", true);
+        assertHasColumn(cursor, "untethered_left_charging", true);
+        assertHasColumn(cursor, "untethered_right_charging", true);
+        assertHasColumn(cursor, "untethered_case_charging", true);
+
+        while (cursor.moveToNext()) {
+            // Check whether metadata data type are blob
+            assertColumnBlob(cursor, "manufacturer_name");
+            assertColumnBlob(cursor, "model_name");
+            assertColumnBlob(cursor, "software_version");
+            assertColumnBlob(cursor, "hardware_version");
+            assertColumnBlob(cursor, "companion_app");
+            assertColumnBlob(cursor, "main_icon");
+            assertColumnBlob(cursor, "is_untethered_headset");
+            assertColumnBlob(cursor, "untethered_left_icon");
+            assertColumnBlob(cursor, "untethered_right_icon");
+            assertColumnBlob(cursor, "untethered_case_icon");
+            assertColumnBlob(cursor, "untethered_left_battery");
+            assertColumnBlob(cursor, "untethered_right_battery");
+            assertColumnBlob(cursor, "untethered_case_battery");
+            assertColumnBlob(cursor, "untethered_left_charging");
+            assertColumnBlob(cursor, "untethered_right_charging");
+            assertColumnBlob(cursor, "untethered_case_charging");
+
+            // Check whether metadata values are migrated to version 102 successfully
+            assertColumnBlobData(cursor, "manufacturer_name", testString.getBytes());
+            assertColumnBlobData(cursor, "model_name", testString.getBytes());
+            assertColumnBlobData(cursor, "software_version", testString.getBytes());
+            assertColumnBlobData(cursor, "hardware_version", testString.getBytes());
+            assertColumnBlobData(cursor, "companion_app", testString.getBytes());
+            assertColumnBlobData(cursor, "main_icon", testString.getBytes());
+            assertColumnBlobData(cursor, "is_untethered_headset", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_left_icon", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_right_icon", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_case_icon", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_left_battery", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_right_battery", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_case_battery", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_left_charging", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_right_charging", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_case_charging", testString.getBytes());
+        }
+    }
+
+    /**
+     * Helper function to check whether the database has the expected column
+     */
+    void assertHasColumn(Cursor cursor, String columnName, boolean hasColumn) {
+        if (hasColumn) {
+            Assert.assertThat(cursor.getColumnIndex(columnName), CoreMatchers.not(-1));
+        } else {
+            Assert.assertThat(cursor.getColumnIndex(columnName), CoreMatchers.is(-1));
+        }
+    }
+
+    /**
+     * Helper function to check whether the column data type is BLOB
+     */
+    void assertColumnBlob(Cursor cursor, String columnName) {
+        Assert.assertThat(cursor.getType(cursor.getColumnIndex(columnName)),
+                CoreMatchers.is(Cursor.FIELD_TYPE_BLOB));
+    }
+
+    /**
+     * Helper function to check the BLOB data in a column is expected
+     */
+    void assertColumnBlobData(Cursor cursor, String columnName, byte[] data) {
+        Assert.assertThat(cursor.getBlob(cursor.getColumnIndex(columnName)),
+                CoreMatchers.is(data));
+    }
+
+    void restartDatabaseManagerHelper() {
+        Metadata data = new Metadata(LOCAL_STORAGE);
+        data.migrated = true;
+        mDatabase.insert(data);
+
+        mDatabaseManager.cleanup();
+        mDatabaseManager.start(mDatabase);
+        // Wait for handler thread finish its task.
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+        // Remove local storage
+        mDatabaseManager.mMetadataCache.remove(LOCAL_STORAGE);
+        mDatabase.delete(LOCAL_STORAGE);
+    }
+
+    void testSetGetProfilePriorityCase(boolean stored, int priority, int expectedPriority,
+            boolean expectedSetResult) {
+        if (stored) {
+            Metadata data = new Metadata(TEST_BT_ADDR);
+            mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data);
+            mDatabase.insert(data);
+        }
+        Assert.assertEquals(expectedSetResult,
+                mDatabaseManager.setProfilePriority(mTestDevice,
+                BluetoothProfile.HEADSET, priority));
+        Assert.assertEquals(expectedPriority,
+                mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.HEADSET));
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+        List<Metadata> list = mDatabase.load();
+
+        // Check number of metadata in the database
+        if (!stored) {
+            if (priority != BluetoothProfile.PRIORITY_OFF
+                    && priority != BluetoothProfile.PRIORITY_ON
+                    && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+                // Database won't be updated
+                Assert.assertEquals(0, list.size());
+                return;
+            }
+        }
+        Assert.assertEquals(1, list.size());
+
+        // Check whether the device is in database
+        restartDatabaseManagerHelper();
+        Assert.assertEquals(expectedPriority,
+                mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.HEADSET));
+
+        mDatabase.deleteAll();
+        // Wait for clear database
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        mDatabaseManager.mMetadataCache.clear();
+    }
+
+    void testSetGetA2dpOptionalCodecsCase(int test, boolean stored, int value, int expectedValue) {
+        if (stored) {
+            Metadata data = new Metadata(TEST_BT_ADDR);
+            mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data);
+            mDatabase.insert(data);
+        }
+        if (test == A2DP_SUPPORT_OP_CODEC_TEST) {
+            mDatabaseManager.setA2dpSupportsOptionalCodecs(mTestDevice, value);
+            Assert.assertEquals(expectedValue,
+                    mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice));
+        } else {
+            mDatabaseManager.setA2dpOptionalCodecsEnabled(mTestDevice, value);
+            Assert.assertEquals(expectedValue,
+                    mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice));
+        }
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+        List<Metadata> list = mDatabase.load();
+
+        // Check number of metadata in the database
+        if (!stored) {
+            // Database won't be updated
+            Assert.assertEquals(0, list.size());
+            return;
+        }
+        Assert.assertEquals(1, list.size());
+
+        // Check whether the device is in database
+        restartDatabaseManagerHelper();
+        if (test == A2DP_SUPPORT_OP_CODEC_TEST) {
+            Assert.assertEquals(expectedValue,
+                    mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice));
+        } else {
+            Assert.assertEquals(expectedValue,
+                    mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice));
+        }
+
+        mDatabase.deleteAll();
+        // Wait for clear database
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        mDatabaseManager.mMetadataCache.clear();
+    }
+
+    void testSetGetCustomMetaCase(boolean stored, int key, byte[] value, boolean expectedResult) {
+        byte[] testValue = "test value".getBytes();
+        int verifyTime = 1;
+        if (stored) {
+            Metadata data = new Metadata(TEST_BT_ADDR);
+            mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data);
+            mDatabase.insert(data);
+            Assert.assertEquals(expectedResult,
+                    mDatabaseManager.setCustomMeta(mTestDevice, key, testValue));
+            verify(mAdapterService).metadataChanged(TEST_BT_ADDR, key, testValue);
+            verifyTime++;
+        }
+        Assert.assertEquals(expectedResult,
+                mDatabaseManager.setCustomMeta(mTestDevice, key, value));
+        if (expectedResult) {
+            // Check for callback and get value
+            verify(mAdapterService, times(verifyTime)).metadataChanged(TEST_BT_ADDR, key, value);
+            Assert.assertEquals(value,
+                    mDatabaseManager.getCustomMeta(mTestDevice, key));
+        } else {
+            Assert.assertNull(mDatabaseManager.getCustomMeta(mTestDevice, key));
+            return;
+        }
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+        // Check whether the value is saved in database
+        restartDatabaseManagerHelper();
+        Assert.assertArrayEquals(value,
+                mDatabaseManager.getCustomMeta(mTestDevice, key));
+
+        mDatabase.deleteAll();
+        // Wait for clear database
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        mDatabaseManager.mMetadataCache.clear();
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/100.json b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/100.json
new file mode 100644
index 0000000..c06fcc1
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/100.json
@@ -0,0 +1,219 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 100,
+    "identityHash": "1f6d4034a042816719ecd2f92b5bbc5c",
+    "entities": [
+      {
+        "tableName": "metadata",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, `a2dpSupportsOptionalCodecs` INTEGER NOT NULL, `a2dpOptionalCodecsEnabled` INTEGER NOT NULL, `a2dp_priority` INTEGER, `a2dp_sink_priority` INTEGER, `hfp_priority` INTEGER, `hfp_client_priority` INTEGER, `hid_host_priority` INTEGER, `pan_priority` INTEGER, `pbap_priority` INTEGER, `map_priority` INTEGER, `sap_priority` INTEGER, `hearing_aid_priority` INTEGER, `map_client_priority` INTEGER, `manufacturer_name` TEXT, `model_name` TEXT, `software_version` TEXT, `hardware_version` TEXT, `companion_app` TEXT, `main_icon` TEXT, `is_unthethered_headset` TEXT, `unthethered_left_icon` TEXT, `unthethered_right_icon` TEXT, `unthethered_case_icon` TEXT, `unthethered_left_battery` TEXT, `unthethered_right_battery` TEXT, `unthethered_case_battery` TEXT, `unthethered_left_charging` TEXT, `unthethered_right_charging` TEXT, `unthethered_case_charging` TEXT, `enhanced_settings_ui_uri` TEXT, PRIMARY KEY(`address`))",
+        "fields": [
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "migrated",
+            "columnName": "migrated",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpSupportsOptionalCodecs",
+            "columnName": "a2dpSupportsOptionalCodecs",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpOptionalCodecsEnabled",
+            "columnName": "a2dpOptionalCodecsEnabled",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "profilePriorites.a2dp_priority",
+            "columnName": "a2dp_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorites.a2dp_sink_priority",
+            "columnName": "a2dp_sink_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorites.hfp_priority",
+            "columnName": "hfp_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorites.hfp_client_priority",
+            "columnName": "hfp_client_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorites.hid_host_priority",
+            "columnName": "hid_host_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorites.pan_priority",
+            "columnName": "pan_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorites.pbap_priority",
+            "columnName": "pbap_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorites.map_priority",
+            "columnName": "map_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorites.sap_priority",
+            "columnName": "sap_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorites.hearing_aid_priority",
+            "columnName": "hearing_aid_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorites.map_client_priority",
+            "columnName": "map_client_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.manufacturer_name",
+            "columnName": "manufacturer_name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.model_name",
+            "columnName": "model_name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.software_version",
+            "columnName": "software_version",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.hardware_version",
+            "columnName": "hardware_version",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.companion_app",
+            "columnName": "companion_app",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.main_icon",
+            "columnName": "main_icon",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.is_unthethered_headset",
+            "columnName": "is_unthethered_headset",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_left_icon",
+            "columnName": "unthethered_left_icon",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_right_icon",
+            "columnName": "unthethered_right_icon",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_case_icon",
+            "columnName": "unthethered_case_icon",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_left_battery",
+            "columnName": "unthethered_left_battery",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_right_battery",
+            "columnName": "unthethered_right_battery",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_case_battery",
+            "columnName": "unthethered_case_battery",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_left_charging",
+            "columnName": "unthethered_left_charging",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_right_charging",
+            "columnName": "unthethered_right_charging",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_case_charging",
+            "columnName": "unthethered_case_charging",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.enhanced_settings_ui_uri",
+            "columnName": "enhanced_settings_ui_uri",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "address"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"1f6d4034a042816719ecd2f92b5bbc5c\")"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/101.json b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/101.json
new file mode 100644
index 0000000..99c0614
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/101.json
@@ -0,0 +1,225 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 101,
+    "identityHash": "628b9033f73db692c575e18a6aea63c3",
+    "entities": [
+      {
+        "tableName": "metadata",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, `a2dpSupportsOptionalCodecs` INTEGER NOT NULL, `a2dpOptionalCodecsEnabled` INTEGER NOT NULL, `a2dp_priority` INTEGER, `a2dp_sink_priority` INTEGER, `hfp_priority` INTEGER, `hfp_client_priority` INTEGER, `hid_host_priority` INTEGER, `pan_priority` INTEGER, `pbap_priority` INTEGER, `pbap_client_priority` INTEGER, `map_priority` INTEGER, `sap_priority` INTEGER, `hearing_aid_priority` INTEGER, `map_client_priority` INTEGER, `manufacturer_name` TEXT, `model_name` TEXT, `software_version` TEXT, `hardware_version` TEXT, `companion_app` TEXT, `main_icon` TEXT, `is_unthethered_headset` TEXT, `unthethered_left_icon` TEXT, `unthethered_right_icon` TEXT, `unthethered_case_icon` TEXT, `unthethered_left_battery` TEXT, `unthethered_right_battery` TEXT, `unthethered_case_battery` TEXT, `unthethered_left_charging` TEXT, `unthethered_right_charging` TEXT, `unthethered_case_charging` TEXT, `enhanced_settings_ui_uri` TEXT, PRIMARY KEY(`address`))",
+        "fields": [
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "migrated",
+            "columnName": "migrated",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpSupportsOptionalCodecs",
+            "columnName": "a2dpSupportsOptionalCodecs",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpOptionalCodecsEnabled",
+            "columnName": "a2dpOptionalCodecsEnabled",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "profilePriorities.a2dp_priority",
+            "columnName": "a2dp_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.a2dp_sink_priority",
+            "columnName": "a2dp_sink_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.hfp_priority",
+            "columnName": "hfp_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.hfp_client_priority",
+            "columnName": "hfp_client_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.hid_host_priority",
+            "columnName": "hid_host_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.pan_priority",
+            "columnName": "pan_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.pbap_priority",
+            "columnName": "pbap_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.pbap_client_priority",
+            "columnName": "pbap_client_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.map_priority",
+            "columnName": "map_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.sap_priority",
+            "columnName": "sap_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.hearing_aid_priority",
+            "columnName": "hearing_aid_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.map_client_priority",
+            "columnName": "map_client_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.manufacturer_name",
+            "columnName": "manufacturer_name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.model_name",
+            "columnName": "model_name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.software_version",
+            "columnName": "software_version",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.hardware_version",
+            "columnName": "hardware_version",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.companion_app",
+            "columnName": "companion_app",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.main_icon",
+            "columnName": "main_icon",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.is_unthethered_headset",
+            "columnName": "is_unthethered_headset",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_left_icon",
+            "columnName": "unthethered_left_icon",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_right_icon",
+            "columnName": "unthethered_right_icon",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_case_icon",
+            "columnName": "unthethered_case_icon",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_left_battery",
+            "columnName": "unthethered_left_battery",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_right_battery",
+            "columnName": "unthethered_right_battery",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_case_battery",
+            "columnName": "unthethered_case_battery",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_left_charging",
+            "columnName": "unthethered_left_charging",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_right_charging",
+            "columnName": "unthethered_right_charging",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.unthethered_case_charging",
+            "columnName": "unthethered_case_charging",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.enhanced_settings_ui_uri",
+            "columnName": "enhanced_settings_ui_uri",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "address"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"628b9033f73db692c575e18a6aea63c3\")"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/102.json b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/102.json
new file mode 100644
index 0000000..11d80c7
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/102.json
@@ -0,0 +1,225 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 102,
+    "identityHash": "fd763bdc49c947fc2b87c89531559d53",
+    "entities": [
+      {
+        "tableName": "metadata",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, `a2dpSupportsOptionalCodecs` INTEGER NOT NULL, `a2dpOptionalCodecsEnabled` INTEGER NOT NULL, `a2dp_priority` INTEGER, `a2dp_sink_priority` INTEGER, `hfp_priority` INTEGER, `hfp_client_priority` INTEGER, `hid_host_priority` INTEGER, `pan_priority` INTEGER, `pbap_priority` INTEGER, `pbap_client_priority` INTEGER, `map_priority` INTEGER, `sap_priority` INTEGER, `hearing_aid_priority` INTEGER, `map_client_priority` INTEGER, `manufacturer_name` BLOB, `model_name` BLOB, `software_version` BLOB, `hardware_version` BLOB, `companion_app` BLOB, `main_icon` BLOB, `is_untethered_headset` BLOB, `untethered_left_icon` BLOB, `untethered_right_icon` BLOB, `untethered_case_icon` BLOB, `untethered_left_battery` BLOB, `untethered_right_battery` BLOB, `untethered_case_battery` BLOB, `untethered_left_charging` BLOB, `untethered_right_charging` BLOB, `untethered_case_charging` BLOB, `enhanced_settings_ui_uri` BLOB, PRIMARY KEY(`address`))",
+        "fields": [
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "migrated",
+            "columnName": "migrated",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpSupportsOptionalCodecs",
+            "columnName": "a2dpSupportsOptionalCodecs",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpOptionalCodecsEnabled",
+            "columnName": "a2dpOptionalCodecsEnabled",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "profilePriorities.a2dp_priority",
+            "columnName": "a2dp_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.a2dp_sink_priority",
+            "columnName": "a2dp_sink_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.hfp_priority",
+            "columnName": "hfp_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.hfp_client_priority",
+            "columnName": "hfp_client_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.hid_host_priority",
+            "columnName": "hid_host_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.pan_priority",
+            "columnName": "pan_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.pbap_priority",
+            "columnName": "pbap_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.pbap_client_priority",
+            "columnName": "pbap_client_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.map_priority",
+            "columnName": "map_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.sap_priority",
+            "columnName": "sap_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.hearing_aid_priority",
+            "columnName": "hearing_aid_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profilePriorities.map_client_priority",
+            "columnName": "map_client_priority",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.manufacturer_name",
+            "columnName": "manufacturer_name",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.model_name",
+            "columnName": "model_name",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.software_version",
+            "columnName": "software_version",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.hardware_version",
+            "columnName": "hardware_version",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.companion_app",
+            "columnName": "companion_app",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.main_icon",
+            "columnName": "main_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.is_untethered_headset",
+            "columnName": "is_untethered_headset",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_icon",
+            "columnName": "untethered_left_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_icon",
+            "columnName": "untethered_right_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_icon",
+            "columnName": "untethered_case_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_battery",
+            "columnName": "untethered_left_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_battery",
+            "columnName": "untethered_right_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_battery",
+            "columnName": "untethered_case_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_charging",
+            "columnName": "untethered_left_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_charging",
+            "columnName": "untethered_right_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_charging",
+            "columnName": "untethered_case_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.enhanced_settings_ui_uri",
+            "columnName": "enhanced_settings_ui_uri",
+            "affinity": "BLOB",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "address"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"fd763bdc49c947fc2b87c89531559d53\")"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java b/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
index 63b6d4d..6882b50 100644
--- a/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
@@ -3,10 +3,11 @@
 import static org.mockito.Mockito.*;
 
 import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/hdp/HealthServiceTest.java b/tests/unit/src/com/android/bluetooth/hdp/HealthServiceTest.java
deleted file mode 100644
index 04023fc..0000000
--- a/tests/unit/src/com/android/bluetooth/hdp/HealthServiceTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.bluetooth.hdp;
-
-import android.bluetooth.BluetoothAdapter;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import com.android.bluetooth.R;
-import com.android.bluetooth.TestUtils;
-import com.android.bluetooth.btservice.AdapterService;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class HealthServiceTest {
-    private HealthService mService = null;
-    private BluetoothAdapter mAdapter = null;
-    private Context mTargetContext;
-
-    @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
-
-    @Mock private AdapterService mAdapterService;
-
-    @Before
-    public void setUp() throws Exception {
-        mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when HealthService is not enabled",
-                mTargetContext.getResources().getBoolean(R.bool.profile_supported_hdp));
-        MockitoAnnotations.initMocks(this);
-        TestUtils.setAdapterService(mAdapterService);
-        TestUtils.startService(mServiceRule, HealthService.class);
-        mService = HealthService.getHealthService();
-        Assert.assertNotNull(mService);
-        // Try getting the Bluetooth adapter
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-        Assert.assertNotNull(mAdapter);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hdp)) {
-            return;
-        }
-        TestUtils.stopService(mServiceRule, HealthService.class);
-        mService = HealthService.getHealthService();
-        Assert.assertNull(mService);
-        TestUtils.clearAdapterService(mAdapterService);
-    }
-
-    @Test
-    public void testInitialize() {
-        Assert.assertNotNull(HealthService.getHealthService());
-    }
-
-    @Test
-    public void testRegisterAppConfiguration() {
-        // Test registering a null config
-        Assert.assertEquals(false, mService.registerAppConfiguration(null, null));
-    }
-}
diff --git a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
index d4a978f..f5f9e76 100644
--- a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
@@ -30,14 +30,16 @@
 import android.media.AudioManager;
 import android.os.Looper;
 import android.os.ParcelUuid;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
 
-import com.android.bluetooth.R;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.internal.R;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -69,6 +71,7 @@
     private BroadcastReceiver mHearingAidIntentReceiver;
 
     @Mock private AdapterService mAdapterService;
+    @Mock private DatabaseManager mDatabaseManager;
     @Mock private HearingAidNativeInterface mNativeInterface;
     @Mock private AudioManager mAudioManager;
 
@@ -78,7 +81,8 @@
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
         Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
-                mTargetContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+                mTargetContext.getResources().getBoolean(
+                    R.bool.config_hearing_aid_profile_supported));
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
 
@@ -111,20 +115,16 @@
         mDeviceQueueMap.put(mLeftDevice, new LinkedBlockingQueue<>());
         mDeviceQueueMap.put(mRightDevice, new LinkedBlockingQueue<>());
         mDeviceQueueMap.put(mSingleDevice, new LinkedBlockingQueue<>());
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_UNDEFINED);
-        mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_UNDEFINED);
-        mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_UNDEFINED);
         doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
                 .getBondState(any(BluetoothDevice.class));
         doReturn(new ParcelUuid[]{BluetoothUuid.HearingAid}).when(mAdapterService)
                 .getRemoteUuids(any(BluetoothDevice.class));
-        HearingAidService.sConnectTimeoutForEachSideMs = 1000;
-        HearingAidService.sCheckWhitelistTimeoutMs = 2000;
     }
 
     @After
     public void tearDown() throws Exception {
-        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid)) {
+        if (!mTargetContext.getResources().getBoolean(
+                            R.bool.config_hearing_aid_profile_supported)) {
             return;
         }
         stopService();
@@ -216,22 +216,30 @@
      */
     @Test
     public void testGetSetPriority() {
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         Assert.assertEquals("Initial device priority",
                 BluetoothProfile.PRIORITY_UNDEFINED,
                 mService.getPriority(mLeftDevice));
 
-        Assert.assertTrue(mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_OFF));
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
         Assert.assertEquals("Setting device priority to PRIORITY_OFF",
                 BluetoothProfile.PRIORITY_OFF,
                 mService.getPriority(mLeftDevice));
 
-        Assert.assertTrue(mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON));
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         Assert.assertEquals("Setting device priority to PRIORITY_ON",
                 BluetoothProfile.PRIORITY_ON,
                 mService.getPriority(mLeftDevice));
 
-        Assert.assertTrue(mService.setPriority(mLeftDevice,
-                BluetoothProfile.PRIORITY_AUTO_CONNECT));
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
         Assert.assertEquals("Setting device priority to PRIORITY_AUTO_CONNECT",
                 BluetoothProfile.PRIORITY_AUTO_CONNECT,
                 mService.getPriority(mLeftDevice));
@@ -284,8 +292,6 @@
                 badBondState, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
         testOkToConnectCase(mSingleDevice,
                 badBondState, badPriorityValue, false);
-        // Restore prirority to undefined for this test device
-        Assert.assertTrue(mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_UNDEFINED));
     }
 
     /**
@@ -294,9 +300,13 @@
     @Test
     public void testOutgoingConnectMissingHearingAidUuid() {
         // Update the device priority so okToConnect() returns true
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_OFF);
-        mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -317,7 +327,9 @@
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
         // Set the device priority to PRIORITY_OFF so connect() should fail
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_OFF);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
 
         // Send a connect request
         Assert.assertFalse("Connect expected to fail", mService.connect(mLeftDevice));
@@ -329,9 +341,13 @@
     @Test
     public void testOutgoingConnectTimeout() {
         // Update the device priority so okToConnect() returns true
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_OFF);
-        mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -345,8 +361,7 @@
                 mService.getConnectionState(mLeftDevice));
 
         // Verify the connection state broadcast, and that we are in Disconnected state
-        verifyConnectionStateIntent(HearingAidService.sConnectTimeoutForEachSideMs * 3,
-                mLeftDevice,
+        verifyConnectionStateIntent(HearingAidStateMachine.sConnectTimeoutMs * 2, mLeftDevice,
                 BluetoothProfile.STATE_DISCONNECTED,
                 BluetoothProfile.STATE_CONNECTING);
         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
@@ -361,9 +376,13 @@
         // Update hiSyncId map
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -389,9 +408,13 @@
         // Update hiSyncId map
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -449,9 +472,13 @@
         // Update hiSyncId map
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -562,9 +589,13 @@
     @Test
     public void testCreateStateMachineStackEvents() {
         // Update the device priority so okToConnect() returns true
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_OFF);
-        mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -623,9 +654,13 @@
     @Test
     public void testDeleteStateMachineUnbondEvents() {
         // Update the device priority so okToConnect() returns true
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_OFF);
-        mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -680,9 +715,13 @@
     @Test
     public void testDeleteStateMachineDisconnectEvents() {
         // Update the device priority so okToConnect() returns true
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_OFF);
-        mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -725,9 +764,13 @@
         // Update hiSyncId map
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_OFF);
 
         generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_CONNECTED,
                 BluetoothProfile.STATE_DISCONNECTED);
@@ -755,9 +798,13 @@
         // Update hiSyncId map
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
 
         generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_CONNECTED,
                 BluetoothProfile.STATE_DISCONNECTED);
@@ -784,8 +831,11 @@
     @Test
     public void firstTimeConnection_shouldConnectToBothDevices() {
         // Update the device priority so okToConnect() returns true
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
         // Send a connect request for left device
@@ -864,9 +914,13 @@
     @Test
     public void getHiSyncId_afterFirstDeviceConnected() {
         // Update the device priority so okToConnect() returns true
-        mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
-        mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
         // Send a connect request
@@ -952,7 +1006,9 @@
         List<BluetoothDevice> prevConnectedDevices = mService.getConnectedDevices();
 
         // Update the device priority so okToConnect() returns true
-        mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.PRIORITY_ON);
         doReturn(true).when(mNativeInterface).connectHearingAid(device);
         doReturn(true).when(mNativeInterface).disconnectHearingAid(device);
 
@@ -1019,7 +1075,9 @@
     private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
             boolean expected) {
         doReturn(bondState).when(mAdapterService).getBondState(device);
-        Assert.assertTrue(mService.setPriority(device, priority));
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEARING_AID))
+                .thenReturn(priority);
         Assert.assertEquals(expected, mService.okToConnect(device));
     }
 
diff --git a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
index 474861e..2512c5e 100644
--- a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
@@ -24,13 +24,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.HandlerThread;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
 
-import com.android.bluetooth.R;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.internal.R;
 
 import org.hamcrest.core.IsInstanceOf;
 import org.junit.After;
@@ -61,7 +62,8 @@
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
         Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
-                mTargetContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+                mTargetContext.getResources().getBoolean(
+                    R.bool.config_hearing_aid_profile_supported));
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
@@ -77,16 +79,14 @@
         mHearingAidStateMachine = new HearingAidStateMachine(mTestDevice, mHearingAidService,
                 mHearingAidNativeInterface, mHandlerThread.getLooper());
         // Override the timeout value to speed up the test
-        mHearingAidStateMachine.sConnectTimeoutMs = 1000;
-        mHearingAidStateMachine.sDisconnectTimeoutMs = 1000;
-        HearingAidService.sConnectTimeoutForEachSideMs = 1000;
-        HearingAidService.sCheckWhitelistTimeoutMs = 2000;
+        mHearingAidStateMachine.sConnectTimeoutMs = 1000;     // 1s
         mHearingAidStateMachine.start();
     }
 
     @After
     public void tearDown() throws Exception {
-        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid)) {
+        if (!mTargetContext.getResources().getBoolean(
+                            R.bool.config_hearing_aid_profile_supported)) {
             return;
         }
         mHearingAidStateMachine.doQuit();
@@ -186,6 +186,7 @@
                 BluetoothDevice.class));
         doReturn(true).when(mHearingAidNativeInterface).disconnectHearingAid(any(
                 BluetoothDevice.class));
+        when(mHearingAidService.isConnectedPeerDevices(mTestDevice)).thenReturn(true);
 
         // Send a connect request
         mHearingAidStateMachine.sendMessage(HearingAidStateMachine.CONNECT, mTestDevice);
@@ -225,6 +226,7 @@
                 BluetoothDevice.class));
         doReturn(true).when(mHearingAidNativeInterface).disconnectHearingAid(any(
                 BluetoothDevice.class));
+        when(mHearingAidService.isConnectedPeerDevices(mTestDevice)).thenReturn(true);
 
         // Inject an event for when incoming connection is requested
         HearingAidStackEvent connStCh =
@@ -255,5 +257,6 @@
         // Check that we are in Disconnected state
         Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HearingAidStateMachine.Disconnected.class));
+        verify(mHearingAidNativeInterface).addToWhiteList(eq(mTestDevice));
     }
 }
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
index 865a215..f32f96f 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
@@ -24,12 +24,13 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.ServiceManager;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
 import android.telephony.PhoneStateListener;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.bluetooth.TestUtils;
 import com.android.internal.telephony.ISub;
 
@@ -72,6 +73,7 @@
         // Stub other methods
         when(mHeadsetService.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(
                 mTelephonyManager);
+        when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
         when(mHeadsetService.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)).thenReturn(
                 mSubscriptionManager);
         mHandlerThread = new HandlerThread("HeadsetStateMachineTestHandlerThread");
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
index 39249a9..ab8bdd6 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
@@ -40,17 +40,19 @@
 import android.os.ParcelUuid;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.espresso.intent.Intents;
-import android.support.test.espresso.intent.matcher.IntentMatchers;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
 import android.telecom.PhoneAccount;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.intent.Intents;
+import androidx.test.espresso.intent.matcher.IntentMatchers;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.hamcrest.Matchers;
 import org.junit.After;
@@ -85,6 +87,7 @@
     private static final int MAX_HEADSET_CONNECTIONS = 5;
     private static final ParcelUuid[] FAKE_HEADSET_UUID = {BluetoothUuid.Handsfree};
     private static final String TEST_PHONE_NUMBER = "1234567890";
+    private static final String TEST_CALLER_ID = "Test Name";
 
     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
@@ -145,6 +148,7 @@
 
     @Spy private HeadsetObjectsFactory mObjectsFactory = HeadsetObjectsFactory.getInstance();
     @Mock private AdapterService mAdapterService;
+    @Mock private DatabaseManager mDatabaseManager;
     @Mock private HeadsetSystemInterface mSystemInterface;
     @Mock private AudioManager mAudioManager;
     @Mock private HeadsetPhoneState mPhoneState;
@@ -275,6 +279,9 @@
     @Test
     public void testConnectFromApi() {
         BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         mBondedDevices.add(device);
         Assert.assertTrue(mHeadsetService.connect(device));
         verify(mObjectsFactory).makeStateMachine(device,
@@ -316,6 +323,9 @@
     @Test
     public void testUnbondDevice_disconnectBeforeUnbond() {
         BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         mBondedDevices.add(device);
         Assert.assertTrue(mHeadsetService.connect(device));
         verify(mObjectsFactory).makeStateMachine(device,
@@ -357,6 +367,9 @@
     @Test
     public void testUnbondDevice_disconnectAfterUnbond() {
         BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         mBondedDevices.add(device);
         Assert.assertTrue(mHeadsetService.connect(device));
         verify(mObjectsFactory).makeStateMachine(device,
@@ -473,12 +486,12 @@
         verifyVirtualCallStartSequenceInvocations(connectedDevices);
         // Virtual call should be preempted by telecom call
         mHeadsetServiceBinder.phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING,
-                TEST_PHONE_NUMBER, 128);
+                TEST_PHONE_NUMBER, 128, "");
         Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
         verifyVirtualCallStopSequenceInvocations(connectedDevices);
         verifyCallStateToNativeInvocation(
                 new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING,
-                        TEST_PHONE_NUMBER, 128), connectedDevices);
+                        TEST_PHONE_NUMBER, 128, ""), connectedDevices);
     }
 
     /**
@@ -575,19 +588,19 @@
                 IntentMatchers.hasData(dialOutUri)), Intents.times(1));
         // Verify that phone state update confirms the dial out event
         mHeadsetServiceBinder.phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_DIALING,
-                TEST_PHONE_NUMBER, 128);
+                TEST_PHONE_NUMBER, 128, "");
         HeadsetCallState dialingCallState =
                 new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING,
-                        TEST_PHONE_NUMBER, 128);
+                        TEST_PHONE_NUMBER, 128, "");
         verifyCallStateToNativeInvocation(dialingCallState, connectedDevices);
         verify(mNativeInterface).atResponseCode(dialingOutDevice,
                 HeadsetHalConstants.AT_RESPONSE_OK, 0);
         // Verify that IDLE phone state clears the dialing out flag
         mHeadsetServiceBinder.phoneStateChanged(1, 0, HeadsetHalConstants.CALL_STATE_IDLE,
-                TEST_PHONE_NUMBER, 128);
+                TEST_PHONE_NUMBER, 128, "");
         HeadsetCallState activeCallState =
                 new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING,
-                        TEST_PHONE_NUMBER, 128);
+                        TEST_PHONE_NUMBER, 128, "");
         verifyCallStateToNativeInvocation(activeCallState, connectedDevices);
         Assert.assertFalse(mHeadsetService.hasDeviceInitiatedDialingOut());
     }
@@ -1072,6 +1085,32 @@
         verifyNoMoreInteractions(mNativeInterface);
     }
 
+    /**
+     * Test to verify the call state and caller information are correctly delivered
+     * {@link BluetoothHeadset#phoneStateChanged(int, int, int, String, int, String, boolean)}
+     */
+    @Test
+    public void testPhoneStateChangedWithIncomingCallState() throws RemoteException {
+        // Connect HF
+        for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
+            BluetoothDevice device = TestUtils.getTestDevice(mAdapter, i);
+            connectTestDevice(device);
+            Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(),
+                    Matchers.containsInAnyOrder(mBondedDevices.toArray()));
+            Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates(
+                    new int[]{BluetoothProfile.STATE_CONNECTED}),
+                    Matchers.containsInAnyOrder(mBondedDevices.toArray()));
+        }
+        List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices();
+        Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray()));
+        // Incoming call update by telecom
+        mHeadsetServiceBinder.phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING,
+                TEST_PHONE_NUMBER, 128, TEST_CALLER_ID);
+        HeadsetCallState incomingCallState = new HeadsetCallState(0, 0,
+                HeadsetHalConstants.CALL_STATE_INCOMING, TEST_PHONE_NUMBER, 128, TEST_CALLER_ID);
+        verifyCallStateToNativeInvocation(incomingCallState, connectedDevices);
+    }
+
     private void startVoiceRecognitionFromHf(BluetoothDevice device) {
         // Start voice recognition
         HeadsetStackEvent startVrEvent =
@@ -1114,6 +1153,9 @@
     }
 
     private void connectTestDevice(BluetoothDevice device) {
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         // Make device bonded
         mBondedDevices.add(device);
         // Use connecting event to indicate that device is connecting
@@ -1178,19 +1220,19 @@
     private void verifyVirtualCallStartSequenceInvocations(List<BluetoothDevice> connectedDevices) {
         // Do not verify HeadsetPhoneState changes as it is verified in HeadsetServiceTest
         verifyCallStateToNativeInvocation(
-                new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0),
+                new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0, ""),
                 connectedDevices);
         verifyCallStateToNativeInvocation(
-                new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0),
+                new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0, ""),
                 connectedDevices);
         verifyCallStateToNativeInvocation(
-                new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0),
+                new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, ""),
                 connectedDevices);
     }
 
     private void verifyVirtualCallStopSequenceInvocations(List<BluetoothDevice> connectedDevices) {
         verifyCallStateToNativeInvocation(
-                new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0),
+                new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, ""),
                 connectedDevices);
     }
 
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
index e5395f2..57a9a2f 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
@@ -29,14 +29,16 @@
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.hamcrest.Matchers;
 import org.junit.After;
@@ -79,6 +81,7 @@
 
     @Spy private HeadsetObjectsFactory mObjectsFactory = HeadsetObjectsFactory.getInstance();
     @Mock private AdapterService mAdapterService;
+    @Mock private DatabaseManager mDatabaseManager;
     @Mock private HeadsetSystemInterface mSystemInterface;
     @Mock private AudioManager mAudioManager;
     @Mock private HeadsetPhoneState mPhoneState;
@@ -147,6 +150,8 @@
         mHeadsetServiceBinder = (IBluetoothHeadset.Stub) mHeadsetService.initBinder();
         Assert.assertNotNull(mHeadsetServiceBinder);
         mHeadsetServiceBinder.setForceScoAudio(true);
+        // Mock database for getPriority()
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
     }
 
     @After
@@ -227,9 +232,6 @@
         testOkToAcceptConnectionCase(mCurrentDevice, badBondState,
                 BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
         testOkToAcceptConnectionCase(mCurrentDevice, badBondState, badPriorityValue, false);
-        // Restore prirority to undefined for this test device
-        Assert.assertTrue(
-                mHeadsetService.setPriority(mCurrentDevice, BluetoothProfile.PRIORITY_UNDEFINED));
     }
 
     /**
@@ -239,6 +241,9 @@
      */
     @Test
     public void testConnectDevice_connectDeviceBelowLimit() {
+        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+                eq(BluetoothProfile.HEADSET)))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
         Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
         verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
@@ -348,6 +353,9 @@
     @Test
     public void testConnectDevice_connectDeviceAboveLimit() {
         ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+                eq(BluetoothProfile.HEADSET)))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
             mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
             Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -397,6 +405,9 @@
      */
     @Test
     public void testConnectAudio_withOneDevice() {
+        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+                eq(BluetoothProfile.HEADSET)))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
         Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
         verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
@@ -446,6 +457,9 @@
     @Test
     public void testConnectAudio_withMultipleDevices() {
         ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+                eq(BluetoothProfile.HEADSET)))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
             mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
             Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -519,6 +533,9 @@
     @Test
     public void testConnectAudio_connectTwoAudioChannelsShouldFail() {
         ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+                eq(BluetoothProfile.HEADSET)))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
             mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
             Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -588,6 +605,9 @@
     @Test
     public void testConnectAudio_firstConnectedAudioDevice() {
         ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+                eq(BluetoothProfile.HEADSET)))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         doAnswer(invocation -> {
             BluetoothDevice[] devicesArray = new BluetoothDevice[connectedDevices.size()];
             return connectedDevices.toArray(devicesArray);
@@ -652,10 +672,13 @@
      */
     @Test
     public void testConnectAudio_deviceDisconnected() {
+        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+                eq(BluetoothProfile.HEADSET)))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
         HeadsetCallState headsetCallState =
                 new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
-                        TEST_PHONE_NUMBER, 128);
+                        TEST_PHONE_NUMBER, 128, "");
         Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
         verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
                 mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService,
@@ -687,10 +710,10 @@
     public void testPhoneStateChange_noDeviceSaveState() throws RemoteException {
         HeadsetCallState headsetCallState =
                 new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
-                        TEST_PHONE_NUMBER, 128);
+                        TEST_PHONE_NUMBER, 128, "");
         mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive,
                 headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber,
-                headsetCallState.mType);
+                headsetCallState.mType, headsetCallState.mName);
         HeadsetTestUtils.verifyPhoneStateChangeSetters(mPhoneState, headsetCallState,
                 ASYNC_CALL_TIMEOUT_MILLIS);
     }
@@ -705,7 +728,10 @@
     public void testPhoneStateChange_oneDeviceSaveState() throws RemoteException {
         HeadsetCallState headsetCallState =
                 new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
-                        TEST_PHONE_NUMBER, 128);
+                        TEST_PHONE_NUMBER, 128, "");
+        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+                eq(BluetoothProfile.HEADSET)))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
         final ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
         // Connect one device
@@ -741,7 +767,7 @@
         // Change phone state
         mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive,
                 headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber,
-                headsetCallState.mType);
+                headsetCallState.mType, headsetCallState.mName);
         // Make sure we notify device about this change
         verify(mStateMachines.get(mCurrentDevice)).sendMessage(
                 HeadsetStateMachine.CALL_STATE_CHANGED, headsetCallState);
@@ -760,8 +786,11 @@
     public void testPhoneStateChange_multipleDevicesSaveState() throws RemoteException {
         HeadsetCallState headsetCallState =
                 new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
-                        TEST_PHONE_NUMBER, 128);
+                        TEST_PHONE_NUMBER, 128, "");
         final ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+                eq(BluetoothProfile.HEADSET)))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
         for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
             mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
             Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -801,7 +830,7 @@
         // Change phone state
         mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive,
                 headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber,
-                headsetCallState.mType);
+                headsetCallState.mType, headsetCallState.mName);
         // Make sure we notify devices about this change
         for (BluetoothDevice device : connectedDevices) {
             verify(mStateMachines.get(device)).sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
@@ -812,6 +841,47 @@
                 ASYNC_CALL_TIMEOUT_MILLIS);
     }
 
+    /**
+     * Test that whether active device been removed after enable silence mode
+     */
+    @Test
+    public void testSetSilenceMode() {
+        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+                eq(BluetoothProfile.HEADSET)))
+                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+        for (int i = 0; i < 2; i++) {
+            mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
+            Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
+            when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+            when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                    BluetoothProfile.STATE_CONNECTED);
+            when(mStateMachines.get(mCurrentDevice).setSilenceDevice(
+                    anyBoolean())).thenReturn(true);
+        }
+        mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+        BluetoothDevice otherDevice = TestUtils.getTestDevice(mAdapter, 1);
+
+        // Test whether active device been removed after enable silence mode.
+        Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice));
+        Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice());
+        Assert.assertTrue(mHeadsetService.setSilenceMode(mCurrentDevice, true));
+        Assert.assertNull(mHeadsetService.getActiveDevice());
+
+        // Test whether active device been resumed after disable silence mode.
+        Assert.assertTrue(mHeadsetService.setSilenceMode(mCurrentDevice, false));
+        Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice());
+
+        // Test that active device should not be changed when silence a non-active device
+        Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice));
+        Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice());
+        Assert.assertTrue(mHeadsetService.setSilenceMode(otherDevice, true));
+        Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice());
+
+        // Test that active device should not be changed when another device exits silence mode
+        Assert.assertTrue(mHeadsetService.setSilenceMode(otherDevice, false));
+        Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice());
+    }
+
     /*
      *  Helper function to test okToAcceptConnection() method
      *
@@ -823,7 +893,8 @@
     private void testOkToAcceptConnectionCase(BluetoothDevice device, int bondState, int priority,
             boolean expected) {
         doReturn(bondState).when(mAdapterService).getBondState(device);
-        Assert.assertTrue(mHeadsetService.setPriority(device, priority));
+        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
+                .thenReturn(priority);
         Assert.assertEquals(expected, mHeadsetService.okToAcceptConnection(device));
     }
 
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
index 80192a6..576d169 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
@@ -31,13 +31,14 @@
 import android.os.HandlerThread;
 import android.os.UserHandle;
 import android.provider.CallLog;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
 import android.telephony.PhoneStateListener;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
@@ -941,6 +942,87 @@
     }
 
     /**
+     * A test to verfiy that we correctly handles AT+BIND event with driver safety case from HF
+     */
+    @Test
+    public void testAtBindWithDriverSafetyEventWhenConnecting() {
+        setUpConnectingState();
+
+        String atString = "1";
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIND, atString, mTestDevice));
+        ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcast(
+                intentArgument.capture(), eq(HeadsetService.BLUETOOTH_PERM));
+        verify(mHeadsetService, times(1)).sendBroadcast(any(), any());
+        Assert.assertEquals(mTestDevice, intentArgument.getValue().getExtra(
+                BluetoothDevice.EXTRA_DEVICE, null));
+        Assert.assertEquals(HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY,
+                intentArgument.getValue().getIntExtra(
+                        BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1));
+        Assert.assertEquals(-1, intentArgument.getValue().getIntExtra(
+                BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -2));
+    }
+
+    /**
+     * A test to verfiy that we correctly handles AT+BIND event with battery level case from HF
+     */
+    @Test
+    public void testAtBindEventWithBatteryLevelEventWhenConnecting() {
+        setUpConnectingState();
+
+        String atString = "2";
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIND, atString, mTestDevice));
+        ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcast(
+                intentArgument.capture(), eq(HeadsetService.BLUETOOTH_PERM));
+        verify(mHeadsetService, times(1)).sendBroadcast(any(), any());
+        Assert.assertEquals(mTestDevice, intentArgument.getValue().getExtra(
+                BluetoothDevice.EXTRA_DEVICE, null));
+        Assert.assertEquals(HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS,
+                intentArgument.getValue().getIntExtra(
+                        BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1));
+        Assert.assertEquals(-1, intentArgument.getValue().getIntExtra(
+                BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -2));
+    }
+
+    /**
+     * A test to verfiy that we correctly handles AT+BIND event with error case from HF
+     */
+    @Test
+    public void testAtBindEventWithErrorEventWhenConnecting() {
+        setUpConnectingState();
+
+        String atString = "err,A,123,,1";
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIND, atString, mTestDevice));
+        ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcast(
+                intentArgument.capture(), eq(HeadsetService.BLUETOOTH_PERM));
+        verify(mHeadsetService, times(1)).sendBroadcast(any(), any());
+        Assert.assertEquals(mTestDevice, intentArgument.getValue().getExtra(
+                BluetoothDevice.EXTRA_DEVICE, null));
+        Assert.assertEquals(HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY,
+                intentArgument.getValue().getIntExtra(
+                        BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1));
+        Assert.assertEquals(-1, intentArgument.getValue().getIntExtra(
+                BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -2));
+    }
+
+    /**
+     * A test to verify that we correctly set AG indicator mask when enter/exit silence mode
+     */
+    @Test
+    public void testSetSilenceDevice() {
+        doNothing().when(mPhoneState).listenForPhoneState(any(BluetoothDevice.class), anyInt());
+        mHeadsetStateMachine.setSilenceDevice(true);
+        mHeadsetStateMachine.setSilenceDevice(false);
+        verify(mPhoneState, times(2)).listenForPhoneState(mTestDevice,
+                PhoneStateListener.LISTEN_NONE);
+    }
+
+    /**
      * Setup Connecting State
      * @return number of times mHeadsetService.sendBroadcastAsUser() has been invoked
      */
diff --git a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
index 5826415..17afcc3 100644
--- a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
@@ -18,10 +18,11 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
index 334597b..b31c4e2 100644
--- a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
@@ -5,6 +5,7 @@
 import static org.mockito.Mockito.*;
 
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAssignedNumbers;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothHeadsetClientCall;
@@ -14,14 +15,17 @@
 import android.content.res.Resources;
 import android.media.AudioManager;
 import android.os.HandlerThread;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.espresso.intent.matcher.IntentMatchers;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+import android.os.Message;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.intent.matcher.IntentMatchers;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
+import com.android.bluetooth.Utils;
 
 import org.hamcrest.core.AllOf;
 import org.hamcrest.core.IsInstanceOf;
@@ -52,6 +56,8 @@
     @Mock
     private AudioManager mAudioManager;
 
+    private NativeInterface mNativeInterface;
+
     private static final int STANDARD_WAIT_MILLIS = 1000;
     private static final int QUERY_CURRENT_CALLS_WAIT_MILLIS = 2000;
     private static final int QUERY_CURRENT_CALLS_TEST_WAIT_MILLIS = QUERY_CURRENT_CALLS_WAIT_MILLIS
@@ -68,10 +74,11 @@
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(2);
         when(mAudioManager.getStreamMaxVolume(anyInt())).thenReturn(10);
         when(mAudioManager.getStreamMinVolume(anyInt())).thenReturn(1);
-        when(mHeadsetClientService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(
+        when(mHeadsetClientService.getAudioManager()).thenReturn(
                 mAudioManager);
         when(mHeadsetClientService.getResources()).thenReturn(mMockHfpResources);
         when(mMockHfpResources.getBoolean(R.bool.hfp_clcc_poll_during_call)).thenReturn(true);
+        mNativeInterface = spy(NativeInterface.getInstance());
 
         // This line must be called to make sure relevant objects are initialized properly
         mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -83,7 +90,8 @@
         mHandlerThread.start();
         // Manage looper execution in main test thread explicitly to guarantee timing consistency
         mHeadsetClientStateMachine =
-                new HeadsetClientStateMachine(mHeadsetClientService, mHandlerThread.getLooper());
+                new HeadsetClientStateMachine(mHeadsetClientService, mHandlerThread.getLooper(),
+                                              mNativeInterface);
         mHeadsetClientStateMachine.start();
     }
 
@@ -329,4 +337,227 @@
         Assert.assertEquals(false, mHeadsetClientStateMachine.getInBandRing());
 
     }
+
+    /* Utility function to simulate HfpClient is connected. */
+    private int setUpHfpClientConnection(int startBroadcastIndex) {
+        // Trigger an incoming connection is requested
+        StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
+        connStCh.device = mTestDevice;
+        mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh);
+        ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(startBroadcastIndex))
+                .sendBroadcast(intentArgument.capture(), anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        startBroadcastIndex++;
+        return startBroadcastIndex;
+    }
+
+    /* Utility function to simulate SLC connection. */
+    private int setUpServiceLevelConnection(int startBroadcastIndex) {
+        // Trigger SLC connection
+        StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
+        slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
+        slcEvent.device = mTestDevice;
+        mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent);
+        ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(startBroadcastIndex))
+                .sendBroadcast(intentArgument.capture(), anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        startBroadcastIndex++;
+        return startBroadcastIndex;
+    }
+
+    /* Utility function: supported AT command should lead to native call */
+    private void runSupportedVendorAtCommand(String atCommand, int vendorId) {
+        // Return true for priority.
+        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.PRIORITY_ON);
+
+        int expectedBroadcastIndex = 1;
+
+        expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
+        expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
+
+        Message msg = mHeadsetClientStateMachine.obtainMessage(
+                HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND, vendorId, 0, atCommand);
+        mHeadsetClientStateMachine.sendMessage(msg);
+
+        verify(mNativeInterface, timeout(STANDARD_WAIT_MILLIS).times(1)).sendATCmd(
+                Utils.getBytesFromAddress(mTestDevice.getAddress()),
+                HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_VENDOR_SPECIFIC_CMD,
+                0, 0, atCommand);
+    }
+
+    /**
+     *  Test: supported vendor specific command: set operation
+     */
+    @LargeTest
+    @Test
+    public void testSupportedVendorAtCommandSet() {
+        int vendorId = BluetoothAssignedNumbers.APPLE;
+        String atCommand = "+XAPL=ABCD-1234-0100,100";
+        runSupportedVendorAtCommand(atCommand, vendorId);
+    }
+
+    /**
+     *  Test: supported vendor specific command: read operation
+     */
+    @LargeTest
+    @Test
+    public void testSupportedVendorAtCommandRead() {
+        int vendorId = BluetoothAssignedNumbers.APPLE;
+        String atCommand = "+APLSIRI?";
+        runSupportedVendorAtCommand(atCommand, vendorId);
+    }
+
+    /* utility function: unsupported vendor specific command shall be filtered. */
+    public void runUnsupportedVendorAtCommand(String atCommand, int vendorId) {
+        // Return true for priority.
+        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.PRIORITY_ON);
+
+        int expectedBroadcastIndex = 1;
+
+        expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
+        expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
+
+        Message msg = mHeadsetClientStateMachine.obtainMessage(
+                HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND, vendorId, 0, atCommand);
+        mHeadsetClientStateMachine.sendMessage(msg);
+
+        verify(mNativeInterface, timeout(STANDARD_WAIT_MILLIS).times(0))
+                .sendATCmd(any(), anyInt(), anyInt(), anyInt(), any());
+    }
+
+    /**
+     *  Test: unsupported vendor specific command shall be filtered: bad command code
+     */
+    @LargeTest
+    @Test
+    public void testUnsupportedVendorAtCommandBadCode() {
+        String atCommand = "+XAAPL=ABCD-1234-0100,100";
+        int vendorId = BluetoothAssignedNumbers.APPLE;
+        runUnsupportedVendorAtCommand(atCommand, vendorId);
+    }
+
+    /**
+     *  Test: unsupported vendor specific command shall be filtered:
+     *  no back to back command
+     */
+    @LargeTest
+    @Test
+    public void testUnsupportedVendorAtCommandBackToBack() {
+        String atCommand = "+XAPL=ABCD-1234-0100,100; +XAPL=ab";
+        int vendorId = BluetoothAssignedNumbers.APPLE;
+        runUnsupportedVendorAtCommand(atCommand, vendorId);
+    }
+
+    /* Utility test function: supported vendor specific event
+     * shall lead to broadcast intent
+     */
+    private void runSupportedVendorEvent(int vendorId, String vendorEventCode,
+            String vendorEventArgument) {
+        // Setup connection state machine to be in connected state
+        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.PRIORITY_ON);
+        int expectedBroadcastIndex = 1;
+        expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
+        expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
+
+        // Simulate a known event arrive
+        String vendorEvent = vendorEventCode + vendorEventArgument;
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
+        event.device = mTestDevice;
+        event.valueString = vendorEvent;
+        mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event);
+
+        // Validate broadcast intent
+        ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastIndex))
+                .sendBroadcast(intentArgument.capture(), anyString());
+        Assert.assertEquals(BluetoothHeadsetClient.ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT,
+                intentArgument.getValue().getAction());
+        Assert.assertEquals(vendorId,
+                intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_VENDOR_ID, -1));
+        Assert.assertEquals(vendorEventCode,
+                intentArgument.getValue().getStringExtra(
+                    BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_CODE));
+        Assert.assertEquals(vendorEvent,
+                intentArgument.getValue().getStringExtra(
+                    BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_FULL_ARGS));
+    }
+
+    /**
+     *  Test: supported vendor specific response: response to read command
+     */
+    @LargeTest
+    @Test
+    public void testSupportedVendorEventReadResponse() {
+        final int vendorId = BluetoothAssignedNumbers.APPLE;
+        final String vendorResponseCode = "+XAPL=";
+        final String vendorResponseArgument = "iPhone,2";
+        runSupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgument);
+    }
+
+    /**
+     *  Test: supported vendor specific response: response to test command
+     */
+    @LargeTest
+    @Test
+    public void testSupportedVendorEventTestResponse() {
+        final int vendorId = BluetoothAssignedNumbers.APPLE;
+        final String vendorResponseCode = "+APLSIRI:";
+        final String vendorResponseArgumentWithSpace = "  2";
+        runSupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgumentWithSpace);
+    }
+
+    /* Utility test function: unsupported vendor specific response shall be filtered out*/
+    public void runUnsupportedVendorEvent(int vendorId, String vendorEventCode,
+            String vendorEventArgument) {
+        // Setup connection state machine to be in connected state
+        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.PRIORITY_ON);
+        int expectedBroadcastIndex = 1;
+        expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
+        expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
+
+        // Simulate an unknown event arrive
+        String vendorEvent = vendorEventCode + vendorEventArgument;
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
+        event.device = mTestDevice;
+        event.valueString = vendorEvent;
+        mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event);
+
+        // Validate no broadcast intent
+        verify(mHeadsetClientService, atMost(expectedBroadcastIndex - 1))
+                .sendBroadcast(any(), anyString());
+    }
+
+    /**
+     * Test unsupported vendor response: bad read response
+     */
+    @LargeTest
+    @Test
+    public void testUnsupportedVendorEventBadReadResponse() {
+        final int vendorId = BluetoothAssignedNumbers.APPLE;
+        final String vendorResponseCode = "+XAAPL=";
+        final String vendorResponseArgument = "iPhone,2";
+        runUnsupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgument);
+    }
+
+    /**
+     * Test unsupported vendor response: bad test response
+     */
+    @LargeTest
+    @Test
+    public void testUnsupportedVendorEventBadTestResponse() {
+        final int vendorId = BluetoothAssignedNumbers.APPLE;
+        final String vendorResponseCode = "+AAPLSIRI:";
+        final String vendorResponseArgument = "2";
+        runUnsupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgument);
+    }
 }
diff --git a/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java b/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
index 618d2b4..eae1c1c 100644
--- a/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
@@ -29,10 +29,11 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java b/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
index 939e068..a3bedf8 100644
--- a/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
@@ -21,14 +21,16 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -51,6 +53,7 @@
     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
     @Mock private AdapterService mAdapterService;
+    @Mock private DatabaseManager mDatabaseManager;
 
     @Before
     public void setUp() throws Exception {
@@ -149,7 +152,9 @@
     private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
             boolean expected) {
         doReturn(bondState).when(mAdapterService).getBondState(device);
-        Assert.assertTrue(mService.setPriority(device, priority));
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HID_HOST))
+                .thenReturn(priority);
 
         // Test when the AdapterService is in non-quiet mode.
         doReturn(false).when(mAdapterService).isQuietModeEnabled();
diff --git a/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java b/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
index 47b72d6..cca340e 100644
--- a/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
+++ b/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
@@ -25,13 +25,14 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.bluetooth.R;
 
 import org.junit.Assert;
diff --git a/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java b/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
index 34c64c5..0a000a2 100644
--- a/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
@@ -17,10 +17,11 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
index b83cf3d..70854d8 100644
--- a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
@@ -27,13 +27,14 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.Suppress;
-import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.bluetooth.R;
+import com.android.bluetooth.btservice.ProfileService;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -41,33 +42,32 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@Suppress  // TODO: enable when b/74609188 is debugged
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class MapClientStateMachineTest {
     private static final String TAG = "MapStateMachineTest";
-    private static final Integer TIMEOUT = 3000;
+
+    private static final int ASYNC_CALL_TIMEOUT_MILLIS = 100;
 
     private BluetoothAdapter mAdapter;
     private MceStateMachine mMceStateMachine = null;
     private BluetoothDevice mTestDevice;
     private Context mTargetContext;
 
-    private FakeMapClientService mFakeMapClientService;
-    private CountDownLatch mConnectedLatch = null;
-    private CountDownLatch mDisconnectedLatch = null;
     private Handler mHandler;
 
+    private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
+
+    @Mock
+    private MapClientService mMockMapClientService;
+
     @Mock
     private MasClient mMockMasClient;
 
-
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -75,16 +75,15 @@
         Assume.assumeTrue("Ignore test when MapClientService is not enabled",
                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce));
 
+        doReturn(mTargetContext.getResources()).when(mMockMapClientService).getResources();
+
         // This line must be called to make sure relevant objects are initialized properly
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         // Get a device for testing
         mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
 
-        mConnectedLatch = new CountDownLatch(1);
-        mDisconnectedLatch = new CountDownLatch(1);
-        mFakeMapClientService = new FakeMapClientService();
         when(mMockMasClient.makeRequest(any(Request.class))).thenReturn(true);
-        mMceStateMachine = new MceStateMachine(mFakeMapClientService, mTestDevice, mMockMasClient);
+        mMceStateMachine = new MceStateMachine(mMockMapClientService, mTestDevice, mMockMasClient);
         Assert.assertNotNull(mMceStateMachine);
         if (Looper.myLooper() == null) {
             Looper.prepare();
@@ -120,22 +119,15 @@
         Log.i(TAG, "in testStateTransitionFromConnectingToDisconnected");
         setupSdpRecordReceipt();
         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED);
-        mMceStateMachine.getCurrentState().processMessage(msg);
+        mMceStateMachine.sendMessage(msg);
 
         // Wait until the message is processed and a broadcast request is sent to
         // to MapClientService to change
         // state from STATE_CONNECTING to STATE_DISCONNECTED
-        boolean result = false;
-        try {
-            result = mDisconnectedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-        // Test that the latch reached zero; i.e., that a broadcast of state-change was received.
-        Assert.assertTrue(result);
-        // When the state reaches STATE_DISCONNECTED, MceStateMachine object is in the process of
-        // being dismantled; i.e., can't rely on getting its current state. That means can't
-        // test its current state = STATE_DISCONNECTED.
+        verify(mMockMapClientService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
+                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
     }
 
     /**
@@ -147,45 +139,51 @@
 
         setupSdpRecordReceipt();
         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
-        mMceStateMachine.getCurrentState().processMessage(msg);
+        mMceStateMachine.sendMessage(msg);
 
         // Wait until the message is processed and a broadcast request is sent to
         // to MapClientService to change
         // state from STATE_CONNECTING to STATE_CONNECTED
-        boolean result = false;
-        try {
-            result = mConnectedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-        // Test that the latch reached zero; i.e., that a broadcast of state-change was received.
-        Assert.assertTrue(result);
+        verify(mMockMapClientService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
+                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
+    }
+
+    /**
+     * Test receiving an empty event report
+     */
+    @Test
+    public void testReceiveEmptyEvent() {
+        setupSdpRecordReceipt();
+        Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
+        mMceStateMachine.sendMessage(msg);
+
+        // Wait until the message is processed and a broadcast request is sent to
+        // to MapClientService to change
+        // state from STATE_CONNECTING to STATE_CONNECTED
+        verify(mMockMapClientService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
+                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
+
+        // Send an empty notification event, verify the mMceStateMachine is still connected
+        Message notification = Message.obtain(mHandler, MceStateMachine.MSG_NOTIFICATION);
+        mMceStateMachine.getCurrentState().processMessage(msg);
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
     }
 
     private void setupSdpRecordReceipt() {
+        // Perform first part of MAP connection logic.
+        verify(mMockMapClientService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendBroadcast(
+                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState());
+
         // Setup receipt of SDP record
-        SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "blah");
+        SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "MasRecord");
         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_SDP_DONE, record);
-        mMceStateMachine.getCurrentState().processMessage(msg);
+        mMceStateMachine.sendMessage(msg);
     }
 
-    private class FakeMapClientService extends MapClientService {
-        @Override
-        void cleanupDevice(BluetoothDevice device) {}
-        @Override
-        public void sendBroadcast(Intent intent, String receiverPermission) {
-            int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
-            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
-            Log.i(TAG, "received broadcast: prevState = " + prevState
-                    + ", state = " + state);
-            if (prevState == BluetoothProfile.STATE_CONNECTING
-                    && state == BluetoothProfile.STATE_CONNECTED) {
-                mConnectedLatch.countDown();
-            } else if (prevState == BluetoothProfile.STATE_CONNECTING
-                    && state == BluetoothProfile.STATE_DISCONNECTED) {
-                mDisconnectedLatch.countDown();
-            }
-        }
-    }
 }
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
index a8ac084..bc25a11 100644
--- a/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
@@ -16,17 +16,22 @@
 
 package com.android.bluetooth.mapclient;
 
+import static org.mockito.Mockito.*;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -52,6 +57,7 @@
 
     @Mock private AdapterService mAdapterService;
     @Mock private MnsService mMockMnsService;
+    @Mock private DatabaseManager mDatabaseManager;
 
     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
@@ -68,6 +74,7 @@
         Assert.assertNotNull(mService);
         cleanUpInstanceMap();
         mAdapter = BluetoothAdapter.getDefaultAdapter();
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
     }
 
     @After
@@ -91,6 +98,17 @@
         Assert.assertTrue(mService.getInstanceMap().isEmpty());
     }
 
+    /**
+     * Mock the priority of a bluetooth device
+     *
+     * @param device - The bluetooth device you wish to mock the priority of
+     * @param priority - The priority value you want the device to have
+     */
+    private void mockDevicePriority(BluetoothDevice device, int priority) {
+        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.MAP_CLIENT))
+                .thenReturn(priority);
+    }
+
     @Test
     public void testInitialize() {
         Assert.assertNotNull(MapClientService.getMapClientService());
@@ -106,6 +124,7 @@
         Assert.assertNull(mService.getInstanceMap().get(device));
 
         // connect a bluetooth device
+        mockDevicePriority(device, BluetoothProfile.PRIORITY_ON);
         Assert.assertTrue(mService.connect(device));
 
         // is the statemachine created
@@ -115,6 +134,25 @@
     }
 
     /**
+     * Test that a PRIORITY_OFF device is not connected to
+     */
+    @Test
+    public void testConnectPriorityOffDevice() {
+        // make sure there is no statemachine already defined for this device
+        BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
+        Assert.assertNull(mService.getInstanceMap().get(device));
+
+        // connect a bluetooth device
+        mockDevicePriority(device, BluetoothProfile.PRIORITY_OFF);
+        Assert.assertFalse(mService.connect(device));
+
+        // is the statemachine created
+        Map<BluetoothDevice, MceStateMachine> map = mService.getInstanceMap();
+        Assert.assertEquals(0, map.size());
+        Assert.assertNull(map.get(device));
+    }
+
+    /**
      * Test connecting MAXIMUM_CONNECTED_DEVICES devices.
      */
     @Test
@@ -131,8 +169,9 @@
             Assert.assertNull(mService.getInstanceMap().get(d));
         }
 
-        // run the test - connect all devices
+        // run the test - connect all devices, set their priorities to on
         for (BluetoothDevice d : list) {
+            mockDevicePriority(d, BluetoothProfile.PRIORITY_ON);
             Assert.assertTrue(mService.connect(d));
         }
 
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java b/tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java
new file mode 100644
index 0000000..5ef2d45
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.mapclient;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+@RunWith(AndroidJUnit4.class)
+public class ObexTimeTest {
+    private static final String TAG = ObexTimeTest.class.getSimpleName();
+
+    private static final String VALID_TIME_STRING = "20190101T121314";
+    private static final String VALID_TIME_STRING_WITH_OFFSET_POS = "20190101T121314+0130";
+    private static final String VALID_TIME_STRING_WITH_OFFSET_NEG = "20190101T121314-0130";
+
+    private static final String INVALID_TIME_STRING_OFFSET_EXTRA_DIGITS = "20190101T121314-99999";
+    private static final String INVALID_TIME_STRING_BAD_DELIMITER = "20190101Q121314";
+
+    // MAP message listing times, per spec, use "local time basis" if UTC offset isn't given. The
+    // ObexTime class parses using the current default timezone (assumed to be the "local timezone")
+    // in the case that UTC isn't provided. However, the Date class assumes UTC ALWAYS when
+    // initializing off of a long value. We have to take that into account when determining our
+    // expected results for time strings that don't have an offset.
+    private static final long LOCAL_TIMEZONE_OFFSET = TimeZone.getDefault().getRawOffset();
+
+    // If you are a positive offset from GMT then GMT is in the "past" and you need to subtract that
+    // offset from the time. If you are negative then GMT is in the future and you need to add that
+    // offset to the time.
+    private static final long VALID_TS = 1546344794000L; // Jan 01, 2019 at 12:13:14 GMT
+    private static final long TS_OFFSET = 5400000L; // 1 Hour, 30 minutes -> milliseconds
+    private static final long VALID_TS_LOCAL_TZ = VALID_TS - LOCAL_TIMEZONE_OFFSET;
+    private static final long VALID_TS_OFFSET_POS = VALID_TS - TS_OFFSET;
+    private static final long VALID_TS_OFFSET_NEG = VALID_TS + TS_OFFSET;
+
+    private static final Date VALID_DATE_LOCAL_TZ = new Date(VALID_TS_LOCAL_TZ);
+    private static final Date VALID_DATE_WITH_OFFSET_POS = new Date(VALID_TS_OFFSET_POS);
+    private static final Date VALID_DATE_WITH_OFFSET_NEG = new Date(VALID_TS_OFFSET_NEG);
+
+    @Test
+    public void createWithValidDateTimeString_TimestampCorrect() {
+        ObexTime timestamp = new ObexTime(VALID_TIME_STRING);
+        Assert.assertEquals("Parsed timestamp must match expected", VALID_DATE_LOCAL_TZ,
+                timestamp.getTime());
+    }
+
+    @Test
+    public void createWithValidDateTimeStringWithPosOffset_TimestampCorrect() {
+        ObexTime timestamp = new ObexTime(VALID_TIME_STRING_WITH_OFFSET_POS);
+        Assert.assertEquals("Parsed timestamp must match expected", VALID_DATE_WITH_OFFSET_POS,
+                timestamp.getTime());
+    }
+
+    @Test
+    public void createWithValidDateTimeStringWithNegOffset_TimestampCorrect() {
+        ObexTime timestamp = new ObexTime(VALID_TIME_STRING_WITH_OFFSET_NEG);
+        Assert.assertEquals("Parsed timestamp must match expected", VALID_DATE_WITH_OFFSET_NEG,
+                timestamp.getTime());
+    }
+
+    @Test
+    public void createWithValidDate_TimestampCorrect() {
+        ObexTime timestamp = new ObexTime(VALID_DATE_LOCAL_TZ);
+        Assert.assertEquals("ObexTime created with a date must return the same date",
+                VALID_DATE_LOCAL_TZ, timestamp.getTime());
+    }
+
+    @Test
+    public void printValidTime_TimestampMatchesInput() {
+        ObexTime timestamp = new ObexTime(VALID_TIME_STRING);
+        Assert.assertEquals("Timestamp as a string must match the input string", VALID_TIME_STRING,
+                timestamp.toString());
+    }
+
+    @Test
+    public void createWithInvalidDelimiterString_TimestampIsNull() {
+        ObexTime timestamp = new ObexTime(INVALID_TIME_STRING_BAD_DELIMITER);
+        Assert.assertEquals("Parsed timestamp was invalid and must result in a null object", null,
+                timestamp.getTime());
+    }
+
+    @Test
+    public void createWithInvalidOffsetString_TimestampIsNull() {
+        ObexTime timestamp = new ObexTime(INVALID_TIME_STRING_OFFSET_EXTRA_DIGITS);
+        Assert.assertEquals("Parsed timestamp was invalid and must result in a null object", null,
+                timestamp.getTime());
+    }
+
+    @Test
+    public void printInvalidTime_ReturnsNull() {
+        ObexTime timestamp = new ObexTime(INVALID_TIME_STRING_BAD_DELIMITER);
+        Assert.assertEquals("Invalid timestamps must return null for toString()", null,
+                timestamp.toString());
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java b/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java
index 66eac21..38d9c58 100644
--- a/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java
+++ b/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java
@@ -20,8 +20,9 @@
 
 import android.media.MediaDescription;
 import android.media.browse.MediaBrowser.MediaItem;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
index 85ad147..ee41320 100644
--- a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
+++ b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
@@ -24,11 +24,12 @@
 import android.media.session.PlaybackState;
 import android.os.HandlerThread;
 import android.os.TestLooperManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -151,23 +152,24 @@
     @Test
     public void testIsReady() {
         MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
-        Assert.assertTrue(wrapper.isReady());
+        Assert.assertTrue(wrapper.isPlaybackStateReady());
+        Assert.assertTrue(wrapper.isMetadataReady());
 
-        // Test isReady() is false when the playback state is null
+        // Test isPlaybackStateReady() is false when the playback state is null
         doReturn(null).when(mMockController).getPlaybackState();
-        Assert.assertFalse(wrapper.isReady());
+        Assert.assertFalse(wrapper.isPlaybackStateReady());
 
         // Restore the old playback state
         doReturn(mTestState.build()).when(mMockController).getPlaybackState();
-        Assert.assertTrue(wrapper.isReady());
+        Assert.assertTrue(wrapper.isPlaybackStateReady());
 
-        // Test isReady() is false when the metadata is null
+        // Test isMetadataReady() is false when the metadata is null
         doReturn(null).when(mMockController).getMetadata();
-        Assert.assertFalse(wrapper.isReady());
+        Assert.assertFalse(wrapper.isMetadataReady());
 
         // Restore the old metadata
         doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
-        Assert.assertTrue(wrapper.isReady());
+        Assert.assertTrue(wrapper.isMetadataReady());
     }
 
     /*
@@ -178,7 +180,8 @@
     public void testControllerUpdate() {
         // Create the wrapper object and register the looper with the timeout handler
         MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
-        Assert.assertTrue(wrapper.isReady());
+        Assert.assertTrue(wrapper.isPlaybackStateReady());
+        Assert.assertTrue(wrapper.isMetadataReady());
         wrapper.registerCallback(mTestCbs);
 
         // Create a new MediaController that has different metadata than the previous controller
diff --git a/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java b/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
index 8b40c62..7f479d4 100644
--- a/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
@@ -17,10 +17,11 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java b/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
index ca96285..6d4574f 100644
--- a/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
@@ -17,10 +17,11 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java b/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
index e3dcf8c..8990eb7 100644
--- a/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
@@ -17,10 +17,11 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java b/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java
index 0f085e8..516235f 100644
--- a/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java
@@ -25,9 +25,10 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java b/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java
index dd33d79..5e9b5ae 100644
--- a/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java
@@ -17,10 +17,11 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/pbapclient/PbapParserTest.java b/tests/unit/src/com/android/bluetooth/pbapclient/PbapParserTest.java
index f8ad6bf..667e2b7 100644
--- a/tests/unit/src/com/android/bluetooth/pbapclient/PbapParserTest.java
+++ b/tests/unit/src/com/android/bluetooth/pbapclient/PbapParserTest.java
@@ -24,9 +24,10 @@
 import android.net.Uri;
 import android.provider.CallLog.Calls;
 import android.provider.ContactsContract;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 
diff --git a/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java b/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java
index 8505673..bc5bffe 100644
--- a/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java
@@ -17,10 +17,11 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;